সঙ্গতি এবং সমান্তরালতা রুবি বিকাশকারীদের জন্য আগের চেয়ে বেশি গুরুত্বপূর্ণ। তারা আমাদের অ্যাপ্লিকেশনগুলিকে দ্রুততর করতে পারে, হার্ডওয়্যারকে ব্যবহার করে যা তাদের সম্পূর্ণ সম্ভাবনার জন্য শক্তি দেয়৷ এই নিবন্ধে, আমরা বর্তমানে প্রতিটি রুবিস্টের কাছে উপলব্ধ সরঞ্জামগুলি এবং রুবি এই বিভাগে শীঘ্রই কী সরবরাহ করার প্রতিশ্রুতি দিয়েছে তাও অন্বেষণ করতে যাচ্ছি৷
সবাই সরাসরি একযোগে ব্যবহার করে না, কিন্তু আমরা সবাই Sidekiq-এর মতো টুলের মাধ্যমে পরোক্ষভাবে এটি ব্যবহার করি। রুবি কনকারেন্সি বোঝা শুধু আপনার নিজের সমাধান তৈরি করতে সাহায্য করবে না; এটি আপনাকে বিদ্যমান সমস্যাগুলি বুঝতে এবং সমস্যা সমাধানে সহায়তা করবে৷
৷কিন্তু প্রথমে আসুন একধাপ পিছিয়ে যাই এবং বড় ছবিটা দেখি।
সঙ্গতি বনাম সমান্তরালতা
এই পদগুলি ঢিলেঢালাভাবে ব্যবহার করা হয়, তবে তাদের আলাদা অর্থ আছে৷
- সঙ্গতি: এক সময়ে অনেকগুলি কাজ করার শিল্প। তাদের মধ্যে দ্রুত স্যুইচ করার মাধ্যমে, এটি ব্যবহারকারীর কাছে মনে হতে পারে যেন তারা একই সাথে ঘটে।
- সমান্তরালতা: আক্ষরিক অর্থে একই সময়ে অনেক কাজ করা। একই সাথে প্রদর্শিত হওয়ার পরিবর্তে, তারা যুগপত।
কনকারেন্সি প্রায়শই আইও ভারী অ্যাপ্লিকেশনের জন্য ব্যবহৃত হয়। উদাহরণস্বরূপ, একটি ওয়েব অ্যাপ নিয়মিতভাবে একটি ডাটাবেসের সাথে যোগাযোগ করতে পারে বা প্রচুর নেটওয়ার্ক অনুরোধ করতে পারে। সঙ্গতি ব্যবহার করে, আমরা আমাদের অ্যাপ্লিকেশনকে প্রতিক্রিয়াশীল রাখতে পারি, এমনকি যখন আমরা আমাদের প্রশ্নের উত্তর দেওয়ার জন্য ডাটাবেসের জন্য অপেক্ষা করি।
এটি সম্ভব কারণ রুবি ভিএম অন্য থ্রেডগুলি চালানোর অনুমতি দেয় যখন কেউ IO এর সময় অপেক্ষা করছে। এমনকি যদি একটি প্রোগ্রামকে কয়েক ডজন অনুরোধ করতে হয়, আমরা যদি একযোগে ব্যবহার করি, অনুরোধগুলি কার্যত একই সময়ে করা হবে৷
অন্যদিকে সমান্তরালতা বর্তমানে রুবি দ্বারা সমর্থিত নয়৷
৷রুবিতে কেন সমান্তরালতা নেই?
আজ, ডিফল্ট রুবি বাস্তবায়ন (সাধারণত এমআরআই বা CRuby বলা হয়) ব্যবহার করে একটি একক রুবি প্রক্রিয়ার মধ্যে সমান্তরালতা অর্জনের কোনো উপায় নেই। রুবি ভিএম একটি লক প্রয়োগ করে (জিভিএম, বা গ্লোবাল ভিএম লক) যা একই সময়ে রুবি কোড চালানো থেকে একাধিক থ্রেডকে বাধা দেয়। এই লকটি ভার্চুয়াল মেশিনের অভ্যন্তরীণ অবস্থা রক্ষা করতে এবং VM ক্র্যাশ হতে পারে এমন পরিস্থিতি রোধ করতে বিদ্যমান। এটি থাকার জন্য একটি দুর্দান্ত জায়গা নয়, তবে সমস্ত আশা হারিয়ে যায় না:রুবি 3 শীঘ্রই আসছে এবং এটি গিল্ড (এই নিবন্ধের শেষ অংশগুলিতে ব্যাখ্যা করা হয়েছে) কোডনামযুক্ত একটি ধারণা প্রবর্তনের মাধ্যমে এই প্রতিবন্ধকতা সমাধান করার প্রতিশ্রুতি দেয়৷
থ্রেড
থ্রেডগুলি হল রুবির একযোগে ওয়ার্কহরস। এগুলিকে কীভাবে ব্যবহার করতে হয় এবং কী কী অসুবিধাগুলি সম্পর্কে সচেতন হতে হবে তা আরও ভালভাবে বোঝার জন্য, আমরা একটি উদাহরণ দিতে যাচ্ছি। আমরা একটি ছোট প্রোগ্রাম তৈরি করব যা একটি API ব্যবহার করে এবং এর ফলাফলগুলিকে একযোগে ব্যবহার করে ডেটাস্টোরে সংরক্ষণ করে৷
আমরা API ক্লায়েন্ট তৈরি করার আগে, আমাদের একটি API প্রয়োজন। নীচে একটি ক্ষুদ্র API এর বাস্তবায়ন রয়েছে যা একটি সংখ্যা গ্রহণ করে এবং প্রদত্ত সংখ্যাটি জোড় বিজোড় হলে প্লেইন টেক্সট হিসাবে প্রতিক্রিয়া জানায়৷ যদি সিনট্যাক্স আপনার কাছে অদ্ভুত মনে হয়, চিন্তা করবেন না। এটার সাথে কনকারেন্সির কোন সম্পর্ক নেই। এটি শুধুমাত্র একটি টুল যা আমরা ব্যবহার করব৷
৷app =
Proc.new do |env|
sleep 0.05
qs = env['QUERY_STRING']
number = Integer(qs.match(/number=(\d+)/)[1])
[
'200',
{ 'Content-Type' => 'text/plain' },
[number.even? ? 'even' : 'odd']
]
end
run app
এই ওয়েব অ্যাপটি চালানোর জন্য আপনাকে র্যাক জেম ইনস্টল করতে হবে, তারপরে rackup config.ru
চালান .
আমাদের একটি মক ডেটাস্টোরও দরকার। এখানে একটি ক্লাস যা একটি কী-মান ডাটাবেসকে অনুকরণ করে:
class Datastore
# ... accessors and initialization omitted ...
def read(key)
data[key]
end
def write(key, value)
data[key] = value
end
end
এখন, আমাদের সমসাময়িক সমাধানের বাস্তবায়নের মধ্য দিয়ে যাওয়া যাক। আমাদের একটি পদ্ধতি আছে, run
, যা একসাথে 1,000টি রেকর্ড নিয়ে আসে এবং সেগুলিকে আমাদের ডেটাস্টোরে সঞ্চয় করে৷
class ThreadPoweredIntegration
# ... accessors and initialization ...
def run
threads = []
(1..1000).each_slice(250) do |subset|
threads << Thread.new do
subset.each do |number|
uri = 'https://localhost:9292/' \
"even_or_odd?number=#{number}"
status, body = AdHocHTTP.new(uri).blocking_get
handle_response(status, body)
rescue Errno::ETIMEDOUT
retry # Try again if the server times out.
end
end
end
threads.each(&:join)
end
# ...
end
আমরা চারটি থ্রেড তৈরি করি, প্রতিটি 250টি রেকর্ড প্রক্রিয়াকরণ করে। আমরা এই কৌশলটি ব্যবহার করি যাতে থার্ড-পার্টি এপিআই বা আমাদের নিজস্ব সিস্টেমকে অভিভূত না করি।
একাধিক থ্রেড ব্যবহার করে একযোগে অনুরোধগুলি করার মাধ্যমে, একটি ক্রমিক বাস্তবায়নে যে সময় লাগবে তার একটি ভগ্নাংশ মোট কার্যকর করতে হবে। একটি HTTP অনুরোধের মাধ্যমে প্রতিষ্ঠা এবং যোগাযোগ করার জন্য প্রয়োজনীয় সমস্ত পদক্ষেপের সময় প্রতিটি থ্রেডের নিষ্ক্রিয়তার মুহূর্ত থাকলেও, রুবি ভিএম একটি ভিন্ন থ্রেডকে চলতে শুরু করার অনুমতি দেয়। এই কারণেই এই বাস্তবায়ন একটি ক্রমিক একের চেয়ে অনেক দ্রুত।
AdHocHTTP
ক্লাস হল একটি সহজবোধ্য HTTP ক্লায়েন্ট যা এই নিবন্ধটির জন্য বিশেষভাবে প্রয়োগ করা হয়েছে যাতে আমাদের শুধুমাত্র থ্রেড দ্বারা চালিত কোড এবং ফাইবার দ্বারা চালিত কোডের মধ্যে পার্থক্যগুলির উপর ফোকাস করার অনুমতি দেওয়া হয়। এটির বাস্তবায়ন নিয়ে আলোচনা করা এই নিবন্ধের সুযোগের বাইরে, তবে আপনি যদি কৌতূহলী হন তবে আপনি এটি এখানে পরীক্ষা করে দেখতে পারেন৷
অবশেষে, আমরা ভিতরের লুপের শেষে সার্ভারের প্রতিক্রিয়া পরিচালনা করি। handle_response
পদ্ধতিটি এখানে দেখায়:
# ... inside the ThreadPoweredIntegration class ...
attr_reader :ds
def initialize
@ds = Datastore.new(even: 0, odd: 0)
end
# ...
def handle_response(status, body)
return if status != '200'
key = body.to_sym
curr_count = ds.read(key)
ds.write(key, curr_count + 1)
end
এই পদ্ধতি সব ঠিক দেখায়, তাই না? চলুন এটি চালাই এবং দেখুন আমাদের ডেটাস্টোরে কী শেষ হয়:
{ even: 497, odd: 489 }
এটি বেশ অদ্ভুত, কারণ আমি নিশ্চিত যে 1 থেকে 1000 এর মধ্যে 500টি জোড় সংখ্যা এবং 500টি বিজোড় সংখ্যা রয়েছে৷ পরবর্তী বিভাগে, আসুন বুঝতে পারি কি ঘটছে এবং সংক্ষিপ্তভাবে এই বাগটি সমাধান করার উপায়গুলির একটি অন্বেষণ করি৷
থ্রেড এবং ডেটা রেস:দ্য ডেভিল ইজ ইন দ্য ডিটেইলস
থ্রেড ব্যবহার করা আমাদের IO ভারী প্রোগ্রামগুলিকে অনেক দ্রুত চালানোর অনুমতি দেয়, তবে সেগুলি সঠিক হওয়াও কঠিন। উপরোক্ত আমাদের ফলাফলে ত্রুটি handle_response
এ রেসের অবস্থার কারণে হয়েছে পদ্ধতি একটি রেস অবস্থা ঘটে যখন দুটি থ্রেড একই ডেটা ম্যানিপুলেট করে।
যেহেতু আমরা একটি শেয়ার্ড রিসোর্সে কাজ করছি (ds
ডেটাস্টোর অবজেক্ট), অ-পারমাণবিক ক্রিয়াকলাপগুলির সাথে আমাদের বিশেষভাবে সতর্ক থাকতে হবে। লক্ষ্য করুন যে আমরা প্রথমে ডেটাস্টোর থেকে পড়ি এবং - দ্বিতীয় বিবৃতিতে - আমরা এটিতে লিখি গণনা 1 দ্বারা বৃদ্ধি করা হয়েছে৷ এটি সমস্যাযুক্ত কারণ আমাদের থ্রেডটি পড়ার পরে কিন্তু লেখার আগে চলা বন্ধ হতে পারে৷ তারপর, যদি অন্য একটি থ্রেড চলে এবং আমাদের আগ্রহী কীটির মান বৃদ্ধি করে, তাহলে মূল থ্রেডটি পুনরায় চালু হলে আমরা একটি পুরানো গণনা লিখব৷
থ্রেড ব্যবহার করার বিপদ কমানোর একটি উপায় হল উচ্চ-স্তরের বিমূর্ততা ব্যবহার করে একটি সমবর্তী বাস্তবায়ন গঠন করা। ব্যবহার করার জন্য বিভিন্ন প্যাটার্ন এবং একটি নিরাপদ থ্রেড-চালিত প্রোগ্রামের জন্য সমসাময়িক-রুবি রত্নটি দেখুন৷
ডেটা রেস ঠিক করার অনেক উপায় আছে। একটি সহজ সমাধান একটি mutex ব্যবহার করা হয়. এই সিঙ্ক্রোনাইজেশন মেকানিজম কোডের একটি প্রদত্ত সেগমেন্টে এক-এক সময়ে অ্যাক্সেস প্রয়োগ করে। এখানে একটি মিউটেক্স ব্যবহারের দ্বারা আমাদের পূর্ববর্তী বাস্তবায়ন সংশোধন করা হয়েছে:
# ... inside ThreadPoweredIntegration class ...
def initialize
# ...
@semaphore = Mutex.new
end
# ...
def handle_response(status, body)
return if status != '200'
key = body.to_sym
semaphore.synchronize do
curr_count = ds.read(key)
ds.write(key, curr_count + 1)
end
end
আপনি যদি রেল অ্যাপ্লিকেশনের ভিতরে থ্রেড ব্যবহার করার পরিকল্পনা করেন, তাহলে অফিসিয়াল গাইড রেলে থ্রেডিং এবং কোড এক্সিকিউশন একটি পড়া আবশ্যক. এই নির্দেশিকাগুলি অনুসরণ করতে ব্যর্থ হলে ডাটাবেস সংযোগগুলি ফাঁসের মতো অপ্রীতিকর পরিণতি হতে পারে৷
আমাদের সঠিক বাস্তবায়ন চালানোর পরে, আমরা প্রত্যাশিত ফলাফল পাই:
{ even: 500, odd: 500 }
একটি মিউটেক্স ব্যবহার করার পরিবর্তে, আমরা থ্রেডগুলিকে সম্পূর্ণভাবে বাদ দিয়ে এবং রুবিতে উপলব্ধ অন্য একযোগী সরঞ্জামের জন্য পৌঁছানোর মাধ্যমে ডেটা রেস থেকে মুক্তি পেতে পারি। পরবর্তী বিভাগে, আমরা IO-ভারী অ্যাপগুলির কর্মক্ষমতা উন্নত করার জন্য একটি প্রক্রিয়া হিসাবে ফাইবারকে দেখতে যাচ্ছি৷
ফাইবার:সঙ্গতির জন্য একটি পাতলা টুল
রুবি ফাইবারস আপনাকে একটি একক থ্রেডের মধ্যে সমবায় সম্মতি অর্জন করতে দেয়। এর মানে হল যে ফাইবারগুলি অগ্রিম করা হয় না এবং প্রোগ্রামটি নিজেই সময়সূচী করতে হবে। কারণ প্রোগ্রামার নিয়ন্ত্রণ করে কখন ফাইবার শুরু হয় এবং বন্ধ হয়, রেসের অবস্থা এড়ানো অনেক সহজ।
থ্রেডের বিপরীতে, IO ঘটলে ফাইবারগুলি আমাদের আরও ভাল কর্মক্ষমতা প্রদান করে না। সৌভাগ্যবশত, রুবি তার IO ক্লাসের মাধ্যমে অ্যাসিঙ্ক্রোনাস রিড এবং লেখেন। এই অ্যাসিঙ্ক পদ্ধতিগুলি ব্যবহার করে আমরা IO অপারেশনগুলিকে আমাদের ফাইবার-ভিত্তিক কোড ব্লক করা থেকে আটকাতে পারি৷
একই দৃশ্য, এখন ফাইবার সহ
আসুন একই উদাহরণের মধ্য দিয়ে যাওয়া যাক, কিন্তু এখন রুবির আইও ক্লাসের অ্যাসিঙ্ক ক্ষমতার সাথে মিলিত ফাইবার ব্যবহার করা হচ্ছে। রুবিতে async IO এর সমস্ত বিবরণ ব্যাখ্যা করা এই নিবন্ধের সুযোগের বাইরে। তারপরও, আমরা এর কাজের প্রয়োজনীয় অংশগুলিকে স্পর্শ করব এবং আপনি যদি কৌতূহলী হন তবে আপনি AdHocHTTP এর প্রাসঙ্গিক পদ্ধতির (একই ক্লায়েন্ট যা আমরা এইমাত্র থ্রেডেড সলিউশনে উপস্থিত হয়েছি) বাস্তবায়নের দিকে নজর দিতে পারেন৷
আমরা run
দেখে শুরু করব আমাদের ফাইবার-চালিত বাস্তবায়নের পদ্ধতি:
class FiberPoweredIntegration
# ... accessors and initialization ...
def run
(1..1000).each_slice(250) do |subset|
Fiber.new do
subset.each do |number|
uri = 'https://127.0.0.1:9292/' \
"even_or_odd?number=#{number}"
client = AdHocHTTP.new(uri)
socket = client.init_non_blocking_get
yield_if_waiting(client,
socket,
:connect_non_blocking_get)
yield_if_waiting(client,
socket,
:write_non_blocking_get)
status, body =
yield_if_waiting(client,
socket,
:read_non_blocking_get)
handle_response(status, body)
ensure
client&.close_non_blocking_get
end
end.resume
end
wait_all_requests
end
# ...
end
আমরা প্রথমে সংখ্যার প্রতিটি উপসেটের জন্য একটি ফাইবার তৈরি করি যা আমরা পরীক্ষা করতে চাই জোড় বা বিজোড়।
তারপর আমরা yield_if_waiting
কল করে নম্বরগুলি লুপ করি . এই পদ্ধতিটি বর্তমান ফাইবার বন্ধ করার জন্য এবং অন্যটিকে পুনরায় চালু করার অনুমতি দেওয়ার জন্য দায়ী৷
আরও লক্ষ্য করুন যে একটি ফাইবার তৈরি করার পরে, আমরা resume
কল করি . এর ফলে ফাইবার চলতে শুরু করে। resume
কল করে তৈরির পরপরই, আমরা 1 থেকে 1000 পর্যন্ত মূল লুপ শেষ হওয়ার আগেই HTTP অনুরোধ করা শুরু করি।
run
শেষে পদ্ধতি, wait_all_requests
-এ একটি কল আছে . এই পদ্ধতিটি ফাইবার নির্বাচন করে যেগুলি চালানোর জন্য প্রস্তুত এবং এছাড়াও আমরা সমস্ত উদ্দিষ্ট অনুরোধগুলি করার গ্যারান্টি দেয়। আমরা এই বিভাগের শেষ সেগমেন্টে এটি দেখে নেব।
এখন, দেখা যাক yield_if_waiting
বিস্তারিত:
# ... inside FiberPoweredIntegration ...
def initialize
@ds = Datastore.new(even: 0, odd: 0)
@waiting = { wait_readable: {}, wait_writable: {} }
end
# ...
def yield_if_waiting(client, socket, operation)
res_or_status = client.send(operation)
is_waiting =
[:wait_readable,
:wait_writable].include?(res_or_status)
return res_or_status unless is_waiting
waiting[res_or_status][socket] = Fiber.current
Fiber.yield
waiting[res_or_status].delete(socket)
yield_if_waiting(client, socket, operation)
rescue Errno::ETIMEDOUT
retry # Try again if the server times out.
end
আমরা প্রথমে আমাদের ক্লায়েন্ট ব্যবহার করে একটি অপারেশন (সংযোগ, পড়া বা লিখতে) করার চেষ্টা করি। দুটি প্রাথমিক ফলাফল সম্ভব:
- সফল: যখন এটি ঘটে, আমরা ফিরে যাই।
- আমরা একটি প্রতীক পেতে পারি: এর মানে আমাদের অপেক্ষা করতে হবে।
কিভাবে একজন "অপেক্ষা" করে?
- ইন্সট্যান্স ভেরিয়েবল
waiting
-এ বর্তমান ফাইবারের সাথে আমাদের সকেট যুক্ত করে আমরা এক ধরনের চেকপয়েন্ট তৈরি করি। (যা একটিHash
)। - আমরা এই জুটিকে একটি সংগ্রহের মধ্যে সংরক্ষণ করি যা IO ধারণ করে পড়ার বা লেখার জন্য অপেক্ষা করে (আমরা দেখতে পাব কেন এটি একটি মুহুর্তে গুরুত্বপূর্ণ), ক্লায়েন্টের কাছ থেকে আমরা যে ফলাফলটি ফিরে পাব তার উপর নির্ভর করে।
- আমরা বর্তমান ফাইবারের এক্সিকিউশন বন্ধ করে দিই, অন্যটিকে চালানোর অনুমতি দিয়ে। বিরতি দেওয়া ফাইবার সংশ্লিষ্ট নেটওয়ার্ক সকেট প্রস্তুত হওয়ার পরে কিছু সময়ে কাজ পুনরায় শুরু করার সুযোগ পাবে। তারপর, IO অপারেশন পুনরায় চেষ্টা করা হবে (এবং এই সময় সফল হবে)।
প্রতিটি রুবি প্রোগ্রাম একটি ফাইবারের ভিতরে চলে যা নিজেই একটি থ্রেডের অংশ (একটি প্রক্রিয়ার ভিতরে সবকিছু)। ফলস্বরূপ, যখন আমরা একটি প্রথম ফাইবার তৈরি করি, এটি চালাই, এবং তারপর কিছু সময়ে ফলন, আমরা প্রোগ্রামের কেন্দ্রীয় অংশের কার্য সম্পাদন পুনরায় শুরু করছি৷
এখন যেহেতু আমরা বুঝতে পারি যে একটি ফাইবার যখন IO অপেক্ষা করে তখন কার্য সম্পাদনের জন্য ব্যবহৃত পদ্ধতিটি, আসুন এই ফাইবার-চালিত বাস্তবায়ন বোঝার জন্য প্রয়োজনীয় শেষ বিটটি অন্বেষণ করি৷
def wait_all_requests
while(waiting[:wait_readable].any? ||
waiting[:wait_writable].any?)
ready_to_read, ready_to_write =
IO.select(waiting[:wait_readable].keys,
waiting[:wait_writable].keys)
ready_to_read.each do |socket|
waiting[:wait_readable][socket].resume
end
ready_to_write.each do |socket|
waiting[:wait_writable][socket].resume
end
end
end
এখানে প্রধান ধারণা হল অপেক্ষা করা (অন্য কথায়, লুপ করা) যতক্ষণ না সমস্ত মুলতুবি IO অপারেশন সম্পূর্ণ হয়।
এটি করতে, আমরা IO.select
ব্যবহার করি . এটি মুলতুবি IO বস্তুর দুটি সংগ্রহ গ্রহণ করে:একটি পড়ার জন্য এবং একটি লেখার জন্য। এটি সেই আইও অবজেক্টগুলিকে ফেরত দেয় যা তাদের কাজ শেষ করেছে। যেহেতু আমরা এই IO বস্তুগুলিকে চালানোর জন্য দায়ী ফাইবারগুলির সাথে যুক্ত করেছি, সেই ফাইবারগুলি পুনরায় চালু করা সহজ৷
আমরা এই পদক্ষেপগুলি পুনরাবৃত্তি করতে থাকি যতক্ষণ না সমস্ত অনুরোধ বরখাস্ত এবং সম্পূর্ণ হয়৷
দ্য গ্র্যান্ড ফিনালে:তুলনীয় পারফরম্যান্স, লকগুলির প্রয়োজন নেই
আমাদের handle_response
পদ্ধতিটি ঠিক একই রকম যেটি প্রাথমিকভাবে থ্রেড ব্যবহার করে কোডে ব্যবহৃত হয় (মিউটেক্স ছাড়া সংস্করণ)। যাইহোক, যেহেতু আমাদের সমস্ত ফাইবার একই থ্রেডের ভিতরে চলে, তাই আমাদের কোন ডেটা রেস থাকবে না। যখন আমরা আমাদের কোড চালাই, তখন আমরা প্রত্যাশিত ফলাফল পাই:
{ even: 500, odd: 500 }
প্রতিবার async IO ব্যবহার করার সময় আপনি সম্ভবত সেই সমস্ত ফাইবার সুইচিং ব্যবসার সাথে মোকাবিলা করতে চান না। সৌভাগ্যবশত, কিছু রত্ন এই সমস্ত কাজকে বিমূর্ত করে এবং ফাইবারের ব্যবহারকে এমন কিছু করে তোলে যা বিকাশকারীকে ভাবতে হবে না। একটি দুর্দান্ত শুরু হিসাবে অ্যাসিঙ্ক প্রকল্পটি দেখুন৷
৷
যখন উচ্চ পরিমাপযোগ্যতা আবশ্যক তখন ফাইবার উজ্জ্বল হয়
যদিও আমরা ছোট আকারের পরিস্থিতিতেও ডেটা রেসের ঝুঁকিগুলিকে কার্যত দূর করার সুবিধাগুলি কাটাতে পারি, উচ্চ মাপযোগ্যতার প্রয়োজন হলে ফাইবারগুলি একটি দুর্দান্ত হাতিয়ার। ফাইবারগুলি থ্রেডের তুলনায় অনেক বেশি হালকা। একই উপলব্ধ সংস্থান দেওয়া হলে, থ্রেড তৈরি করা একটি সিস্টেমকে ফাইবারের চেয়ে অনেক তাড়াতাড়ি আবিষ্ট করবে। বিষয়ে একটি চমৎকার অন্বেষণের জন্য, আমরা দ্য জার্নি টু ওয়ান মিলিয়ন উপস্থাপনাটি সুপারিশ করি রুবি কোর টিমের স্যামুয়েল উইলিয়ামস দ্বারা।
গিল্ড - রুবিতে সমান্তরাল প্রোগ্রামিং
এখন পর্যন্ত আমরা রুবিতে একত্রিত হওয়ার জন্য দুটি দরকারী টুল দেখেছি। যাইহোক, তাদের কোনটিই বিশুদ্ধ গণনার কর্মক্ষমতা উন্নত করতে পারে না। এর জন্য আপনার প্রয়োজন হবে সত্যিকারের সমান্তরালতা, যা বর্তমানে রুবিতে নেই (এখানে আমরা এমআরআই বিবেচনা করছি, ডিফল্ট বাস্তবায়ন)।
এটি "গিল্ডস" নামে একটি নতুন বৈশিষ্ট্য আসার সাথে রুবি 3-তে পরিবর্তন হতে পারে। বিশদ বিবরণ এখনও অস্পষ্ট, কিন্তু নিম্নলিখিত বিভাগগুলিতে আমরা দেখব যে কীভাবে এই কাজের অগ্রগতি বৈশিষ্ট্যটি রুবিতে সমান্তরালতার অনুমতি দেওয়ার প্রতিশ্রুতি দেয়৷
কিভাবে গিল্ড কাজ করতে পারে
সমসাময়িক/সমান্তরাল সমাধানগুলি বাস্তবায়ন করার সময় ব্যথার একটি উল্লেখযোগ্য উত্স হল ভাগ করা মেমরি। থ্রেডের বিভাগে, আমরা ইতিমধ্যে দেখেছি যে একটি স্লিপ তৈরি করা এবং কোড লেখা কতটা সহজ যা প্রথম নজরে নিরীহ মনে হতে পারে কিন্তু আসলে এতে সূক্ষ্ম বাগ রয়েছে৷
Koichi Sasada - রুবি কোর টিমের সদস্য নতুন গিল্ড বৈশিষ্ট্যের বিকাশের নেতৃত্ব দিচ্ছেন - একটি সমাধান ডিজাইন করতে কঠোর পরিশ্রম করছেন যা একাধিক থ্রেডের মধ্যে মেমরি ভাগ করে নেওয়ার বিপদগুলি মোকাবেলা করে৷ 2018 RubyConf-এ তার উপস্থাপনায়, তিনি ব্যাখ্যা করেছেন যে গিল্ড ব্যবহার করার সময় কেউ কেবল পরিবর্তনযোগ্য বস্তুগুলি ভাগ করতে সক্ষম হবে না। মূল ধারণাটি শুধুমাত্র অপরিবর্তনীয় বস্তুগুলিকে বিভিন্ন গিল্ডের মধ্যে ভাগ করার অনুমতি দিয়ে ডেটা রেস প্রতিরোধ করা।
গিল্ডগুলির মধ্যে ভাগ করা মেমরির কিছু পরিমাপের অনুমতি দেওয়ার জন্য রুবিতে বিশেষায়িত ডেটা স্ট্রাকচার চালু করা হবে, তবে এটি ঠিক কীভাবে কাজ করতে চলেছে তার বিশদটি এখনও পুরোপুরি প্রকাশ করা হয়নি। এছাড়াও একটি API থাকবে যা বস্তুকে অনুলিপি বা গিল্ডের মধ্যে স্থানান্তরিত করার অনুমতি দেবে, এবং একটি ভিন্ন গিল্ডে স্থানান্তরিত হওয়ার পরে একটি বস্তুকে রেফারেন্স করা থেকে বাধা দেওয়ার জন্য একটি সুরক্ষা ব্যবস্থা।
একটি সাধারণ দৃশ্য অন্বেষণ করতে গিল্ড ব্যবহার করা
এমন অনেক পরিস্থিতি রয়েছে যেখানে আপনি চান যে আপনি গণনাগুলিকে সমান্তরালভাবে চালানোর মাধ্যমে গতি বাড়াতে পারেন। আসুন কল্পনা করি যে আমাদের একই ডেটাসেটের গড় এবং গড় গণনা করতে হবে।
নীচের উদাহরণটি দেখায় কিভাবে আমরা গিল্ডগুলির সাথে এটি করতে পারি। মনে রাখবেন যে এই কোডটি বর্তমানে কাজ করে না এবং গিল্ড প্রকাশ করার পরেও এটি কখনই কাজ নাও করতে পারে৷
# A frozen array of numeric values is an immutable object.
dataset = [88, 43, 37, 85, 84, 38, 13, 84, 17, 87].freeze
# The overhead of using guilds will probably be
# considerable, so it will only make sense to
# parallelize work when a dataset is large / when
# performing lots of operations.
g1 = Guild.new do
mean = dataset.reduce(:+).fdiv(dataset.length)
Guild.send_to(:mean, Guild.parent)
end
g2 = Guild.new do
median = Median.calculate(dataset.sort)
Guild.send_to(:median, Guild.parent)
end
results = {}
# Every Ruby program will be run inside a main guild;
# therefore, we can also receive messages in the main
# section of our program.
Guild.receive(:mean, :median) do |tag, result|
results[tag] = result
end
সংক্ষেপ করা হচ্ছে
সামঞ্জস্য এবং সমান্তরালতা রুবির প্রধান শক্তি নয়, তবে এমনকি এই বিভাগে ভাষাটি এমন সরঞ্জাম সরবরাহ করে যা সম্ভবত বেশিরভাগ ব্যবহারের ক্ষেত্রে মোকাবেলা করার জন্য যথেষ্ট ভাল। রুবি 3 আসছে এবং মনে হচ্ছে গিল্ড আদিম প্রবর্তনের সাথে জিনিসগুলি আরও ভাল হয়ে উঠবে। আমার মতে, রুবি এখনও অনেক পরিস্থিতিতে একটি খুব উপযুক্ত পছন্দ, এবং এর সম্প্রদায়টি ভাষাকে আরও ভাল করে তোলার ক্ষেত্রে স্পষ্টতই কঠোর। আসুন কি আসছে তার জন্য মাটিতে কান রাখি!