রুবি ম্যাজিকের আগের এডিশনে আমরা দেখিয়েছিলাম কীভাবে আপনি একটি চ্যাট সিস্টেম ইমপ্লিমেন্ট করতে পারেন একাধিক প্রসেস ব্যবহার করে। এইবার আমরা দেখাব কীভাবে আপনি একই জিনিস একাধিক থ্রেড ব্যবহার করে করতে পারেন।
কুইক রিক্যাপ
আপনি যদি বেসিক সেটআপের ফুল এক্সপ্লানেশন চান তবে আগের আর্টিকেল চেক আউট করুন। কিন্তু দ্রুত মনে করাতে: আমাদের চ্যাট সিস্টেম এভাবে দেখে:
আমরা আগের মতো একই ক্লায়েন্ট ব্যবহার করছি:
# 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
সার্ভারের বেসিক সেটআপ একই:
# server_threads.rb
# $ ruby server_threads.rb
require 'socket'
puts 'Starting server on port 2000'
server = TCPServer.open(2000)
এই আর্টিকেলের উদাহরণগুলোর ফুল সোর্স কোড গিটহাবে উপলব্ধ, যাতে আপনি নিজে এক্সপেরিমেন্ট করতে পারেন।
মাল্টি-থ্রেডেড চ্যাট সার্ভার
এখন আমরা সেটি পাচ্ছি যেটি মাল্টি-প্রসেস ইমপ্লিমেন্টেশনের থেকে আলাদা। মাল্টি-থ্রেডিং ব্যবহার করে আমরা একই রুবি প্রসেসে একাধিক জিনিস একসাথে করতে পারি। আমরা সেটি করব একাধিক থ্রেড স্পন করে যারা কাজ করবে।
থ্রেড
একটি থ্রেড স্বাধীনভাবে রান করে, একটি প্রসেসের ভেতরে কোড এক্সিকিউট করে। একাধিক থ্রেড একই প্রসেসে বাস করতে পারে এবং সেগুলো মেমরি শেয়ার করতে পারে।
ইনকামিং চ্যাট মেসেজ স্টোর করার জন্য কিছু স্টোরেজ দরকার। আমরা একটি প্লেইন Array ব্যবহার করব, কিন্তু আমাদের একটি Mutex ও দরকার যাতে শুধু একটি থ্রেড একই সময়ে মেসেজ পরিবর্তন করতে পারে (আমরা দেখব Mutex কীভাবে কাজ করে)।
mutex = Mutex.new
messages = []
পরবর্তী আমরা একটি লুপ শুরু করব যেখানে আমরা চ্যাট ক্লায়েন্ট থেকে ইনকামিং কানেকশন এক্সেপ্ট করব। একবার কানেকশন স্থাপন হলে, আমরা একটি থ্রেড স্পন করব যেটি সেটি ক্লায়েন্ট কানেকশন থেকে ইনকামিং এবং আউটগোইং মেসেজ হ্যান্ডল করবে।
Thread.new কল ব্লক করে যতক্ষণ না server.accept কিছু রিটার্ন করে, এবং তারপর ফলোইং ব্লক নতুন তৈরি থ্রেডে ইয়িল্ড করে। থ্রেডের কোড তারপর প্রথম লাইন পড়ে যেটি পাঠানো হয়েছে এবং সেটি নিকনেম হিসেবে স্টোর করে। অবশেষে ইটা মেসেজ পাঠানো এবং পাওয়া শুরু করে।
loop do
Thread.new(server.accept) do |socket|
nickname = read_line_from(socket)
# Send incoming message (coming up)
# Read incoming messages (coming up)
end
end
Mutex
একটি মিউটেক্স হলো একটি অবজেক্ট যা একাধিক থ্রেডকে কোঅর্ডিনেট করতে দেয় কীভাবে সেগুলো শেয়ারড রিসোর্স ব্যবহার করবে, যেমন একটি অ্যারে। একটি থ্রেড ইন্ডিকেট করতে পারে যে সেটি এক্সেস চায়, এবং এই সময় অন্য থ্রেড শেয়ারড রিসোর্স এক্সেস করতে পারবে না।
সার্ভার সকেট থেকে ইনকামিং মেসেজ পড়ে। এটা synchronize ব্যবহার করে মেসেজ স্টোরে লক পায়, যাতে সেটি নিরাপদে একটি মেসেজ যোগ করতে পারে মেসেজ Array-এ।
# Read incoming messages
while incoming = read_line_from(socket)
mutex.synchronize do
messages.push(
:time => Time.now,
:nickname => nickname,
:text => incoming
)
end
end
শেষে, একটি Thread স্পন করা হয় যেটি কন্টিনিউয়াসলি একটি লুপে রান করে, নিশ্চিত করতে যে সার্ভার tarafından প্রাপ্ত সব নতুন মেসেজ ক্লায়েন্টে পাঠানো হচ্ছে। আবার সেটি লক পায় যাতে সেটি জানে অন্যান্য থ্রেড ইন্টারফেয়ার করছে না। এক টিক লুপ শেষ হলে সেটি একটু স্লিপ করে এবং তারপর কন্টিনিউ করে।
# Send incoming message
Thread.new do
sent_until = Time.now
loop do
messages_to_send = mutex.synchronize do
get_messages_to_send(nickname, messages, sent_until).tap do
sent_until = Time.now
end
end
messages_to_send.each do |message|
socket.puts "#{message[:nickname]}: #{message[:text]}"
end
sleep 0.2
end
end
গ্লোবাল ইন্টারপ্রেটার লক
আপনি শুনে থাকতে পারেন রুবি "রিয়াল" থ্রেডিং করতে পারে না রুবির গ্লোবাল ইন্টারপ্রেটার লক (GIL) এর কারণে। এটি আংশিক সত্য। GIL হলো সকল রুবি কোড এক্সিকিউশনের চারপাশে একটি লক যা একটি রুবি প্রসেসকে একাধিক সিপিইউ কাঙ্কারেন্টলি ব্যবহার করতে বাধা দেয়। IO অপারেশন (যেমন এই আর্টিকেলে আমরা ব্যবহার করেছি নেটওয়ার্ক কানেকশন) GIL-এর বাইরে অপারেট করে, যাতে আপনি এই কেসে ডিসেন্ট কাঙ্কারেন্সি অর্জন করতে পারেন।
উপসংহার
এখন আমাদের একটি চ্যাট সার্ভার আছে যেটি একটি সিঙ্গেল প্রসেসে রান করছে কানেকশন প্রতি একটি থ্রেড ব্যবহার করে। এটা মাল্টি-প্রসেস ইমপ্লিমেন্টেশনের চেয়ে অনেক কম রিসোর্স ব্যবহার করবে। আপনি কোডের ডিটেইলস দেখতে বা ট্রাই করতে চান তবে উদাহরণ কোডটি এখানে পাবেন।
এই সিরিজের ফাইনাল আর্টিকেলে আমরা এই একই চ্যাট সার্ভার ইমপ্লিমেন্ট করব একটি সিঙ্গেল থ্রেড এবং একটি ইভেন্ট লুপ ব্যবহার করে। সিদ্ধান্তিকভাবে এটা থ্রেড ইমপ্লিমেন্টেশনের চেয়ে কম রিসোর্স ব্যবহার করবে!