রুবিতে থ্রেড কী?
থ্রেড আপনার রুবি প্রোগ্রামকে একসাথে একাধিক কাজ করতে দেয়।
যেমন:
- একাধিক ফাইল পড়া
- একাধিক ওয়েব রিকোয়েস্ট হ্যান্ডল করা
- একাধিক API কানেকশন করা
থ্রেড ব্যবহার করার ফলস্বরূপ, আপনার একটি মাল্টি-থ্রেডেড রুবি প্রোগ্রাম হবে, যা দ্রুত কাজ শেষ করতে পারবে।
কিন্তু একটা সতর্কতা…
MRI (Matz's Ruby Interpreter)-এ, রুবি অ্যাপ্লিকেশন রান করার ডিফল্ট পদ্ধতিতে, আপনি শুধু থ্রেডের সুবিধা পাবেন I/O-বাউন্ড অ্যাপ্লিকেশন রান করার সময়।
এই সীমাবদ্ধতা রয়েছে GIL (Global Interpreter Lock)-এর কারণে।
অল্টারনেটিভ রুবি ইন্টারপ্রেটার যেমন JRuby বা Rubinius মাল্টি-থ্রেডিংর পুরো সুবিধা নেয়।

তাহলে, থ্রেড কী?
থ্রেড হচ্ছে ওয়ার্কার, বা এক্সিকিউশন ইউনিট।
প্রতিটি প্রসেসের কমপক্ষে একটি থ্রেড থাকে এবং আপনি ডিমান্ডে আরও তৈরি করতে পারেন।
আমি জানি আপনি একটি কোড উদাহরণ দেখতে চান।
কিন্তু সেটির আগে, আমাদের CPU-বাউন্ড এবং I/O-বাউন্ড অ্যাপ্লিকেশনের পার্থক্য নিয়ে কথা বলতে হবে।
I/O-বাউন্ড অ্যাপ্লিকেশন
একটি I/O-বাউন্ড অ্যাপ হলো যেটিকে এক্সটার্নাল রিসোর্সের জন্য অপেক্ষা করতে হয়:
- একটি API রিকোয়েস্ট
- ডেটাবেস (কুয়েরি রেজাল্ট)
- একটি ডিস্ক রিড
একটি থ্রেড রিসোর্স উপলব্ধ হওয়া পর্যন্ত অপেক্ষা করার সিদ্ধান্ত নিতে পারে। এর মানে অন্য একটি থ্রেড রান করে তার কাজ করতে পারে এবং অপেক্ষা করতে সময় বigte পারবে না।
একটি I/O-বাউন্ড অ্যাপ-এর উদাহরণ হলো একটি ওয়েব ক্রলার।
প্রতিটি রিকোয়েস্টের জন্য, ক্রলারকে সার্ভারের রেসপন্সের জন্য অপেক্ষা করতে হয়, এবং অপেক্ষা করার সময় এটি কিছু করতে পারে না।
কিন্তু আপনি যদি থ্রেড ব্যবহার করেন…
আপনি একসাথে ৪টি রিকোয়েস্ট করতে পারেন এবং রেসপন্সগুলো আসার সাথে সাথে হ্যান্ডল করতে পারেন, যা আপনাকে পেজগুলো দ্রুত ফেচ করতে দেবে।
এখন আপনার কোড উদাহরণের সময়।
রুবি থ্রেড তৈরি করা
আপনি Thread.new কল করে একটি নতুন রুবি থ্রেড তৈরি করতে পারেন।
নিশ্চিত করুন আপনি একটি ব্লক পাস করেছেন যেটিতে এই থ্রেডের রান করার কোড রয়েছে।
Thread.new { puts "hello from thread" }
খুব সহজ, সেটি?
তবে।
আপনার যদি নিচের কোড থাকে তবে আপনি লক্ষ্য করবেন থ্রেড থেকে কোনো আউটপুট নেই:
t = Thread.new { puts 10**10 } puts "hello"
সমস্যাটি হলো রুবি থ্রেডের ফিনিশ হওয়ার জন্য অপেক্ষা করে না।
আপনাকে আপনার থ্রেডের ওপর join মেথড কল করতে হবে উপরের কোড ফিক্স করার জন্য:
t = Thread.new { puts 10**10 } puts "hello" t.join
আপনি যদি একাধিক থ্রেড তৈরি করতে চান তবে আপনি সেগুলো একটি অ্যারেতে রাখতে পারেন এবং প্রতিটি থ্রেডের ওপর join কল করতে পারেন।
উদাহরণ:
threads = [] 10.times { threads << Thread.new { puts 1 } } threads.each(&:join)
রুবি থ্রেড এক্সপ্লোরেশনের সময় আপনি ডকুমেন্টেশনটি উপকারী পেতে পারেন:
https://ruby-doc.org/core-2.5.0/Thread.html
থ্রেড এবং এক্সেপশন
যদি একটি এক্সেপশন একটি থ্রেডের ভেতরে হয় তবে এটি সাইলেন্টলি মরবে আপনার প্রোগ্রাম বন্ধ না করে বা কোনো এরর মেসেজ দেখাবে না।
এখানে একটি উদাহরণ:
Thread.new { raise 'hell' }
ডিবাগিং উদ্দেশ্যে, আপনি আপনার প্রোগ্রামটি রুকতে চাইতে পারেন যখন কিছু খারাপ হয়। সেটি করার জন্য আপনি Thread-এ নিচের ফ্ল্যাগটি true সেট করতে পারেন:
Thread.abort_on_exception = true
নিশ্চিত করুন আপনি আপনার থ্রেড তৈরি করার আগে এই ফ্ল্যাগটি সেট করেছেন 🙂
থ্রেড পুল
মান লিন আপনার প্রসেস করার জন্য সैंकড়ো আইটেম রয়েছে, প্রতিটির জন্য একটি থ্রেড শুরু করলে আপনার সিস্টেম রিসোর্স ধ্বংস হয়ে যাবে।
এটি দেখাবে এমন:
pages_to_crawl = %w( index about contact ... ) pages_to_crawl.each do |page| Thread.new { puts page } end
আপনি যদি এটি করেন তবে আপনি সার্ভারের বিপক্ষে সैंकড়ো কানেকশন লঞ্চ করবেন, তাই এটা সম্ভবত ভালো আইডিয়া নয়।
একটি সমাধান হলো থ্রেড পুল ব্যবহার করা।
থ্রেড পুল আপনাকে যেকোনো সময় একটিভ থ্রেডের সংখ্যা কন্ট্রোল করতে দেয়।
আপনি নিজের পুল বানাতে পারেন, কিন্তু আমি এর পরামর্শ দিব না। নিচের উদাহরণে আমরা celluloid gem ব্যবহার করছি এটি করতে।
নোট: Celluloid বর্তমানে unmaintained, কিন্তু ওয়ার্কার পুলের সাধারণ আইডিয়া এখনও প্রযোজ্য।
require 'celluloid' class Worker include Celluloid def process_page(url) puts url end end pages_to_crawl = %w( index about contact products ... ) worker_pool = Worker.pool(size: 5) # রিটার্ন ভ্যালু কালেক্ট করতে হলে 'futures' চেক আউট করুন pages_to_crawl.each do |page| worker_pool.process_page(page) end
এই বার শুধু ৫টি থ্রেড চলবে, এবং যেগুলো ফিনিশ হবে সেগুলো পরবর্তী আইটেম পিক করবে।
রেস কন্ডিশন এবং অন্যান্য হাজার্ড
এটি সব খুব কুল শুনতে পারে কিন্তু আপনি আপনার কোডের সারাকতরে থ্রেড ছিটিয়ে দাওয়ার আগে আপনাকে জানতে হবে সমকায়িক কোডের সাথে কিছু সমস্যা রয়েছে।
উদাহরণস্বরূপ, থ্রেডগুলো রেস কন্ডিশন-এ আক্রান্ত হয় সহজে।
একটি রেস কন্ডিশন হলো যখন জিনিসগুলো অর্ডার বাইরে হয়ে গেলে এবং গুলগুলি করে।
আরেকটি সমস্যা যা হতে পারে সেটি হলো ডেডলক। এটি হলো যখন একটি থ্রেড এক্সক্লুসিভ অ্যাক্সেস ধরে রাখে (একটি লকিং সিস্টেম মিউটেক্স ব্যবহার করে) কোনো রিসোর্সের এবং কখনো তা রিলিজ করে না, যা সেটিকে সকল অন্যান্য থ্রেডের জন্য অ্যাক্সেসবility না করে দেয়।
এই ইস্যুগুলো এড়াতে, র ও থ্রেড এড়িয়ে যাওয়া ভালো এবং যেই জেমগুলো ইতিমধ্যে ডিটেইলস হ্যান্ডেল করে সেগুলো ব্যবহার করা ভালো।
আরও থ্রেডিং জেমস
আমরা ইতিমধ্যে celluloid ব্যবহার করেছি আমাদের থ্রেড পুলের জন্য, কিন্তু আরও অনেক কনকারেন্সি-ফোকাসড জেম রয়েছে যেগুলো আপনাকে চেক আউট করা উচিত:
- https://github.com/grosser/parallel
- https://github.com/chadrem/workers
- https://github.com/ruby-concurrency/concurrent-ruby
ওকে সেটাই, আশা করি আপনি রুবি থ্রেড নিয়ে একদুই জিনিস শিখলেন!
এই আর্টিকেলটি উপকারী লাগলে দয়া করে শেয়ার করুন আপনার বন্ধুদের সাথে যাতে তারা ও শিখতে পারে 🙂