কম্পিউটার

কনকারেন্সি ডিপ ডাইভ:বহু-প্রক্রিয়া

মাস্টারিং কনকারেন্সি সম্পর্কিত একটি পূর্ববর্তী রুবি ম্যাজিক নিবন্ধে, আমরা সঙ্গতি অর্জনের তিনটি পদ্ধতির একটি ভূমিকা দিয়েছিলাম যা রুবি বিকাশকারী হিসাবে আমাদের কাছে উপলব্ধ। এই নিবন্ধটি তিনটি অংশের সিরিজের প্রথম যেখানে আমরা প্রতিটি পদ্ধতিতে গভীরভাবে ডুব দিই৷

প্রথমত:মাল্টি-প্রক্রিয়া . এই পদ্ধতির সাহায্যে একটি মাস্টার প্রসেস নিজেকে একাধিক কর্মী প্রক্রিয়ার সাথে যুক্ত করে। কর্মী প্রক্রিয়া প্রকৃত কাজ করে, যখন মাস্টার শ্রমিকদের পরিচালনা করেন।

এই নিবন্ধের উদাহরণগুলিতে ব্যবহৃত সম্পূর্ণ উত্স কোডটি GitHub-এ উপলব্ধ, তাই আপনি নিজেই এটি পরীক্ষা করতে পারেন৷

আসুন একটি চ্যাট সিস্টেম তৈরি করি!

একটি চ্যাট সিস্টেম তৈরি করা একযোগে ডুব দেওয়ার একটি ভাল উপায়। আমাদের একটি চ্যাট সিস্টেমের একটি সার্ভার উপাদান প্রয়োজন যা একাধিক ক্লায়েন্টের সাথে সংযোগ বজায় রাখতে সক্ষম। এটি আমাদের একটি ক্লায়েন্ট থেকে অন্য সমস্ত সংযুক্ত ক্লায়েন্টদের কাছে প্রাপ্ত বার্তাগুলি বিতরণ করার অনুমতি দেবে৷

আমাদের চ্যাট সার্ভার বাম ট্যাবে চলছে। সঠিক ট্যাবে দুটি চ্যাট ক্লায়েন্ট চলছে। ক্লায়েন্টের দ্বারা পাঠানো যেকোনো বার্তা অন্য সব ক্লায়েন্টরা গ্রহণ করবে।

চ্যাট ক্লায়েন্ট

এই নিবন্ধটি চ্যাট সার্ভারের উপর ফোকাস করে, তবে এটির সাথে যোগাযোগ করার জন্য প্রথমে আমাদের একটি চ্যাট ক্লায়েন্টের প্রয়োজন হবে। নিম্নলিখিত কোডটি আমাদের খুব সাধারণ ক্লায়েন্ট হবে। (আরো একটি সম্পূর্ণ উদাহরণ GitHub-এ পাওয়া যাবে।)

# client.rb
# $ ruby client.rb
require 'socket'
client = TCPSocket.open(ARGV[0], 2000)
 
Thread.new do
  while line = client.gets
    puts line.chop
  end
end
 
while input = STDIN.gets.chomp
  client.puts input
end

ক্লায়েন্ট পোর্ট 2000 এ চলমান একটি সার্ভারে একটি TCP সংযোগ খোলে। সংযুক্ত হলে, এটি একটি থ্রেড তৈরি করে যা puts করবে। সার্ভার যা কিছু পাঠায়, তাই চ্যাট টার্মিনাল আউটপুটে দৃশ্যমান হয়। অবশেষে, একটি সময় লুপ আছে যা সার্ভারে আপনার টাইপ করা যেকোনো লাইন পাঠায়, যা এটি অন্য সমস্ত সংযুক্ত ক্লায়েন্টকে পাঠাবে।

চ্যাট সার্ভার

এই উদাহরণে একজন ক্লায়েন্ট অন্য ক্লায়েন্টদের সাথে যোগাযোগ করার জন্য একটি চ্যাট সার্ভারের সাথে সংযোগ স্থাপন করে। তিনটি একযোগে পদ্ধতির জন্য আমরা রুবির স্ট্যান্ডার্ড লাইব্রেরি থেকে একই TCP সার্ভার ব্যবহার করব৷

# server_processes.rb
# $ ruby server_processes.rb
require 'socket'
 
puts 'Starting server on port 2000'
 
server = TCPServer.open(2000)

এই বিন্দু পর্যন্ত কোডটি তিনটি কনকারেন্সি মডেলের জন্য একই। প্রতিটি মডেলের চ্যাট সার্ভারকে তখন দুটি পরিস্থিতি পরিচালনা করতে হবে:

  1. ক্লায়েন্টদের কাছ থেকে নতুন সংযোগ গ্রহণ করুন।
  2. ক্লায়েন্টদের কাছ থেকে বার্তা গ্রহণ করুন এবং অন্য সমস্ত ক্লায়েন্টদের কাছে পাঠান।

একটি মাল্টি-প্রসেস চ্যাট সার্ভার

একটি মাল্টি-প্রসেস চ্যাট সার্ভারের সাথে এই দুটি দৃশ্য পরিচালনা করতে, আমরা প্রতি ক্লায়েন্ট সংযোগে একটি প্রক্রিয়া তৈরি করব। এই প্রক্রিয়াটি সেই ক্লায়েন্টের জন্য পাঠানো এবং প্রাপ্ত সমস্ত বার্তা পরিচালনা করবে। আমরা মূল সার্ভার প্রক্রিয়াকে কাঁটা দিয়ে এই প্রক্রিয়াগুলি তৈরি করতে পারি৷

ফর্কিং প্রসেস

যখন আপনি কাঁটাচামচ পদ্ধতিতে কল করেন, তখন এটি বর্তমান প্রক্রিয়াটির একটি অনুলিপি তৈরি করে যার সাথে প্রক্রিয়াটি ঠিক একই অবস্থায় রয়েছে।

একটি কাঁটাযুক্ত প্রক্রিয়ার নিজস্ব প্রক্রিয়া আইডি থাকে এবং top এর মতো একটি টুলে আলাদাভাবে দৃশ্যমান হবে বা কার্যকলাপ মনিটর. এটি এই মত কিছু দেখায়:

আপনি যে প্রক্রিয়াটি দিয়ে শুরু করেন তাকে বলা হয় মাস্টার প্রক্রিয়া, এবং যে প্রক্রিয়াগুলি মাস্টার প্রক্রিয়ার বাইরে চলে যায় সেগুলিকে কর্মী প্রক্রিয়া বলা হয়৷

যেহেতু এই নতুন কাঁটাযুক্ত কর্মী প্রক্রিয়াগুলি সত্যিই পৃথক প্রক্রিয়া, তাই আমরা তাদের এবং মাস্টার প্রক্রিয়ার মধ্যে মেমরি ভাগ করতে পারি না। তাদের মধ্যে যোগাযোগের জন্য আমাদের কিছু দরকার।

ইউনিক্স পাইপ

প্রক্রিয়াগুলির মধ্যে যোগাযোগ করতে আমরা ইউনিক্স পাইপ ব্যবহার করব। একটি ইউনিক্স পাইপ দুটি প্রক্রিয়ার মধ্যে বাইটের একটি দ্বি-মুখী স্ট্রিম সেট আপ করে এবং আপনি এটি একটি প্রক্রিয়া থেকে অন্য প্রক্রিয়ায় ডেটা পাঠাতে ব্যবহার করতে পারেন। সৌভাগ্যবশত, রুবি এই পাইপগুলির চারপাশে একটি সুন্দর মোড়ক অফার করে যাতে আমাদের চাকাটি পুনরায় উদ্ভাবনের প্রয়োজন হয় না৷

নিম্নলিখিত উদাহরণে আমরা রুবি-তে একটি পাইপ সেট আপ করি - একটি পড়া এবং লেখার শেষে- এবং আমরা fork মাস্টার প্রক্রিয়া। ব্লকের মধ্যে কোড যা fork-এ পাস করা হয়েছে কাঁটাচামচ প্রক্রিয়ায় চলছে। এই ব্লকের পরে মূল প্রক্রিয়া চলতে থাকে। তারপরে আমরা কাঁটাযুক্ত একটি থেকে মূল প্রক্রিয়াটিতে একটি বার্তা লিখি।

reader, writer = IO.pipe
 
fork do
  # This is running in the forked process.
  writer.puts 'Hello from the forked process'
end
 
# This is running in the original process, it will puts the
# message from the forked process.
puts reader.gets

পাইপ ব্যবহার করে আমরা পৃথক প্রক্রিয়াগুলির মধ্যে যোগাযোগ করতে পারি যদিও প্রক্রিয়াগুলি একে অপরের থেকে সম্পূর্ণ বিচ্ছিন্ন থাকে৷

চ্যাট সার্ভারের বাস্তবায়ন

প্রথমে আমরা সমস্ত ক্লায়েন্ট এবং তাদের "রাইটারদের" (পাইপের লেখার শেষ) জন্য পাইপের ট্র্যাক রাখার জন্য একটি অ্যারে সেট আপ করি, যাতে আমরা ক্লায়েন্টদের সাথে যোগাযোগ করতে পারি। তারপরে আমরা নিশ্চিত করি যে ক্লায়েন্টদের থেকে আসা সমস্ত বার্তা অন্য সমস্ত ক্লায়েন্টদের কাছে পাঠানো হয়েছে।

client_writers = []
master_reader, master_writer = IO.pipe
 
write_incoming_messages_to_child_processes(master_reader, client_writers)

আপনি write_incoming_messages_to_child_processes এর বাস্তবায়ন খুঁজে পেতে পারেন GitHub-এ আপনি যদি এটি কীভাবে কাজ করে তার বিশদ বিবরণ দেখতে চান।

নতুন সংযোগ গ্রহণ করা হচ্ছে

আমাদের ইনকামিং সংযোগ গ্রহণ করতে হবে এবং পাইপ সেট আপ করতে হবে। নতুন লেখককে client_writers-এ ঠেলে দেওয়া হবে অ্যারে মূল প্রক্রিয়াটি অ্যারের মাধ্যমে লুপ করতে সক্ষম হবে এবং প্রতিটি কর্মী প্রক্রিয়াকে তার পাইপে লিখে একটি বার্তা পাঠাতে পারবে৷

তারপরে আমরা মাস্টার প্রক্রিয়াটি ফর্ক করি, এবং ফর্কড ওয়ার্কার প্রক্রিয়ার মধ্যে থাকা কোডটি ক্লায়েন্ট সংযোগ পরিচালনা করবে৷

loop do
  while socket = server.accept
    # Create a client reader and writer so that the master
    # process can write messages back to us.
    client_reader, client_writer = IO.pipe
 
    # Put the client writer on the list of writers so the
    # master process can write to them.
    client_writers.push(client_writer)
 
    # Fork child process, everything in the fork block
    # only runs in the child process.
    fork do
      # Handle connection
    end
  end
end

ক্লায়েন্ট সংযোগগুলি পরিচালনা করা

আমাদের ক্লায়েন্ট সংযোগও পরিচালনা করতে হবে।

কাঁটাযুক্ত প্রক্রিয়াটি ক্লায়েন্টের কাছ থেকে ডাকনাম পাওয়ার মাধ্যমে শুরু হয় (ক্লায়েন্ট ডিফল্টরূপে ডাকনাম পাঠায়)। এর পরে এটি write_incoming_messages_to_client এ একটি থ্রেড শুরু করে যা মূল প্রক্রিয়া থেকে বার্তা শোনে।

অবশেষে, কাঁটাযুক্ত প্রক্রিয়াটি একটি লুপ শুরু করে যা আগত বার্তাগুলি শোনে এবং সেগুলিকে মাস্টার প্রক্রিয়াতে পাঠায়। মাস্টার প্রক্রিয়া নিশ্চিত করে যে অন্য কর্মী প্রক্রিয়া বার্তা গ্রহণ করে।

nickname = read_line_from(socket)
puts "#{Process.pid}: Accepted connection from #{nickname}"
 
write_incoming_messages_to_client(nickname, client_reader, socket)
 
# Read incoming messages from the client.
while incoming = read_line_from(socket)
  master_writer.puts "#{nickname}: #{incoming}"
end
 
puts "#{Process.pid}: Disconnected #{nickname}"

একটি কাজের চ্যাট সিস্টেম

এখন পুরো চ্যাট সিস্টেম কাজ করে! কিন্তু আপনি দেখতে পাচ্ছেন, মাল্টিপ্রসেসিং ব্যবহার করে এমন একটি প্রোগ্রাম লেখা বেশ জটিল এবং প্রচুর সম্পদ ব্যবহার করে। উল্টো দিক হল যে এটি খুব শক্তিশালী। যদি একটি শিশু প্রক্রিয়া ক্র্যাশ করে তবে বাকি সিস্টেমটি কাজ করে। আপনি উদাহরণ কোডটি চালিয়ে এবং kill -9 <process-id> চালিয়ে এটি চেষ্টা করতে পারেন একটি প্রক্রিয়ায় (আপনি সার্ভারের লগ আউটপুটে প্রক্রিয়া আইডি খুঁজে পেতে পারেন)।

পরবর্তী নিবন্ধে আমরা শুধুমাত্র থ্রেড ব্যবহার করে একই চ্যাট সিস্টেম প্রয়োগ করব, যাতে আমরা শুধুমাত্র একটি প্রক্রিয়া এবং কম মেমরি ব্যবহার করে একই বৈশিষ্ট্য সহ একটি সার্ভার চালাতে পারি৷


  1. C++ এ কিল প্রসেস

  2. কিভাবে পোর্টে প্রসেস কিল করবেন?

  3. LSM.EXE কি

  4. রুবিতে ইউনিক্স ডেমনের একটি তাত্ত্বিক ভূমিকা