कंकरेंसी और पैरेललिज़्म Ruby डेवलपर्स के लिए पहले से कहीं अधिक महत्वपूर्ण हैं। वे हमारे एप्लिकेशन को तेज़ बना सकते हैं, उस हार्डवेयर का पूरी क्षमता से उपयोग करके जो उन्हें शक्ति देता है। इस लेख में, हम हर Rubyist के लिए वर्तमान में उपलब्ध टूल्स का पता लगाने जा रहे हैं और यह भी कि Ruby जल्द ही इस विभाग में क्या देने का वादा करता है।
हर कोई सीधे कंकरेंसी का उपयोग नहीं करता, लेकिन हम सभी Sidekiq जैसे टूल्स के माध्यम से अप्रत्यक्ष रूप से इसका उपयोग करते हैं। Ruby कंकरेंसी को समझना न केवल आपको अपने स्वयं के समाधान बनाने में मदद करेगा; यह आपको मौजूदा समाधानों को समझने और समस्या निवारण में मदद करेगा।
लेकिन पहले आइए एक कदम पीछे हटें और बड़ी तस्वीर देखें।
कंकरेंसी बनाम पैरेललिज़्म
इन शब्दों का ढीले ढंग से उपयोग किया जाता है, लेकिन उनके अलग-अलग अर्थ हैं।
- कंकरेंसी: एक समय में कई कार्य करने की कला। उनके बीच तेजी से स्विच करके, यह उपयोगकर्ता को ऐसा प्रतीत हो सकता है जैसे वे एक साथ होते हैं।
- पैरेललिज़्म: शाब्दिक रूप से एक ही समय में कई कार्य करना। केवल प्रतीत होने के बजाय, वे एक साथ होते हैं।
कंकरेंसी का उपयोग अक्सर उन एप्लिकेशन के लिए किया जाता है जो IO-हैवी हैं। उदाहरण के लिए, एक वेब ऐप नियमित रूप से डेटाबेस के साथ इंटरैक्ट कर सकता है या बहुत सारे नेटवर्क रिक्वेस्ट कर सकता है। कंकरेंसी का उपयोग करके, हम अपने एप्लिकेशन को रिस्पॉन्सिव रख सकते हैं, तब भी जब हम डेटाबेस के हमारे क्वेरी का जवाब देने की प्रतीक्षा करते हैं।
यह संभव है क्योंकि Ruby VM अन्य थ्रेड्स को चलाने की अनुमति देता है जबकि एक IO के दौरान प्रतीक्षा कर रहा है। भले ही एक प्रोग्राम को दर्जनों रिक्वेस्ट करनी हों, यदि हम कंकरेंसी का उपयोग करते हैं, तो रिक्वेस्ट्स लगभग एक ही समय में की जाएंगी।
दूसरी ओर, पैरेललिज़्म वर्तमान में Ruby द्वारा समर्थित नहीं है।
Ruby में पैरेललिज़्म क्यों नहीं?
आज, डिफ़ॉल्ट Ruby कार्यान्वयन (आमतौर पर MRI या CRuby कहा जाता है) का उपयोग करके एकल Ruby प्रक्रिया के भीतर पैरेललिज़्म प्राप्त करने का कोई तरीका नहीं है। Ruby VM एक लॉक (GVM, या ग्लोबल VM लॉक) लागू करता है जो कई थ्रेड्स को एक ही समय में Ruby कोड चलाने से रोकता है। यह लॉक वर्चुअल मशीन की आंतरिक स्थिति की रक्षा के लिए और उन परिदृश्यों को रोकने के लिए मौजूद है जो VM को क्रैश कर सकते हैं। यह एक अच्छी स्थिति नहीं है, लेकिन सारी उम्मीद खत्म नहीं हुई है: Ruby 3 जल्द ही आ रहा है और यह गिल्ड नामक एक अवधारणा को पेश करके इस बाधा को हल करने का वादा करता है (इस लेख के अंतिम खंडों में समझाया गया है)।
थ्रेड्स
थ्रेड्स Ruby के कंकरेंसी वर्कहॉर्स हैं। उन्हें कैसे उपयोग करें और किन नुकसानों से अवगत रहें, इसे बेहतर ढंग से समझने के लिए, हम एक उदाहरण देने जा रहे हैं। हम एक छोटा प्रोग्राम बनाएंगे जो एक 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
इस वेब ऐप को चलाने के लिए आपको rack जेम इंस्टॉल करना होगा, फिर rackup config.ru निष्पादित करें।
हमें एक मॉक डेटास्टोर की भी आवश्यकता है। यहाँ एक क्लास है जो की-वैल्यू डेटाबेस का अनुकरण करती है:
class Datastore
# ... एक्सेसर्स और इनिशियलाइज़ेशन छोड़ा गया ...
def read(key)
data[key]
end
def write(key, value)
data[key] = value
end
end
अब, आइए हमारे कंकरेंट समाधान के कार्यान्वयन को देखें। हमारे पास एक मेथड है, run, जो कंकरेंटली 1,000 रिकॉर्ड्स लाता है और उन्हें हमारे डेटास्टोर में संग्रहीत करता है।
class ThreadPoweredIntegration
# ... एक्सेसर्स और इनिशियलाइज़ेशन ...
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 # सर्वर टाइमआउट होने पर फिर से प्रयास करें।
end
end
end
threads.each(&:join)
end
# ...
end
हम चार थ्रेड्स बनाते हैं, प्रत्येक 250 रिकॉर्ड्स प्रोसेस करता है। हम इस रणनीति का उपयोग थर्ड-पार्टी API या हमारे अपने सिस्टम को अभिभूत न करने के लिए करते हैं।
कई थ्रेड्स का उपयोग करके रिक्वेस्ट्स को कंकरेंटली बनाकर, कुल निष्पादन एक सीक्वेंशियल कार्यान्वयन की तुलना में समय के एक अंश में होगा। जबकि प्रत्येक थ्रेड में HTTP रिक्वेस्ट स्थापित करने और संचार करने के लिए आवश्यक सभी चरणों के दौरान निष्क्रियता के क्षण होते हैं, Ruby VM एक अलग थ्रेड को चलाना शुरू करने की अनुमति देता है। यही कारण है कि यह कार्यान्वयन एक सीक्वेंशियल कार्यान्वयन की तुलना में बहुत तेज़ है।
AdHocHTTPक्लास इस लेख के लिए विशेष रूप से कार्यान्वित एक सीधा HTTP क्लाइंट है ताकि हम केवल थ्रेड्स द्वारा संचालित कोड और फाइबर्स द्वारा संचालित कोड के बीच अंतर पर ध्यान केंद्रित कर सकें। इसके कार्यान्वयन पर चर्चा करना इस लेख के दायरे से बाहर है, लेकिन यदि आप उत्सुक हैं तो आप इसे यहां देख सकते हैं।
अंत में, हम आंतरिक लूप के अंत तक सर्वर की प्रतिक्रिया को हैंडल करते हैं। यहाँ मेथड handle_response कैसा दिखता है:
# ... ThreadPoweredIntegration क्लास के अंदर ...
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 से बढ़ाकर लिखते हैं। यह समस्याग्रस्त है क्योंकि हमारा थ्रेड रीड के बाद लेकिन राइट से पहले चलना बंद कर सकता है। फिर, यदि दूसरा थ्रेड चलता है और उस की की वैल्यू को बढ़ाता है जिसमें हम रुचि रखते हैं, तो जब मूल थ्रेड फिर से शुरू होगा तो हम एक आउट-ऑफ-डेट काउंट लिखेंगे।
थ्रेड्स के उपयोग के खतरों को कम करने का एक तरीका उच्च-स्तरीय अमूर्तताओं का उपयोग करके कंकरेंट कार्यान्वयन को संरचित करना है। concurrent-ruby जेम को विभिन्न पैटर्न का उपयोग करने और एक सुरक्षित थ्रेड-संचालित प्रोग्राम के लिए देखें।
डेटा रेस को ठीक करने के कई तरीके हैं। एक सरल समाधान म्यूटेक्स का उपयोग करना है। यह सिंक्रनाइज़ेशन मैकेनिज्म कोड के दिए गए सेगमेंट के लिए एक-एक समय पर पहुंच को लागू करता है। यहाँ म्यूटेक्स के उपयोग से तय हमारा पिछला कार्यान्वयन है:
# ... ThreadPoweredIntegration क्लास के अंदर ...
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
यदि आप Rails एप्लिकेशन के अंदर थ्रेड्स का उपयोग करने की योजना बना रहे हैं, तो आधिकारिक गाइड Threading and Code Execution in Rails अवश्य पढ़ें। इन दिशानिर्देशों का पालन न करने से बहुत अप्रिय परिणाम हो सकते हैं, जैसे डेटाबेस कनेक्शन लीक होना।
हमारे सही किए गए कार्यान्वयन को चलाने के बाद, हमें अपेक्षित परिणाम मिलता है:
{ even: 500, odd: 500 }
म्यूटेक्स का उपयोग करने के बजाय, हम थ्रेड्स को पूरी तरह से छोड़कर और Ruby में उपलब्ध एक अन्य कंकरेंसी टूल का उपयोग करके डेटा रेस से छुटकारा पा सकते हैं। अगले खंड में, हम Fiber को IO-हैवी ऐप्स के प्रदर्शन को बेहतर बनाने के तंत्र के रूप में देखेंगे।
फाइबर: कंकरेंसी के लिए एक पतला टूल
Ruby फाइबर्स आपको एक ही थ्रेड के भीतर कोऑपरेटिव कंकरेंसी प्राप्त करने देते हैं। इसका मतलब है कि फाइबर्स प्रीएम्प्टेड नहीं हैं और प्रोग्राम को स्वयं शेड्यूलिंग करनी होगी। क्योंकि प्रोग्रामर नियंत्रित करता है कि फाइबर्स कब शुरू और बंद होते हैं, रेस कंडीशन से बचना बहुत आसान है।
थ्रेड्स के विपरीत, फाइबर्स हमें IO होने पर बेहतर प्रदर्शन नहीं देते। सौभाग्य से, Ruby अपने IO क्लास के माध्यम से एसिंक्रोनस रीड्स और राइट्स प्रदान करता है। इन एसिंक मेथड्स का उपयोग करके हम IO ऑपरेशन्स को हमारे फाइबर-आधारित कोड को ब्लॉक करने से रोक सकते हैं।
वही परिदृश्य, अब फाइबर्स के साथ
आइए उसी उदाहरण को देखें, लेकिन अब फाइबर्स का उपयोग Ruby के IO क्लास की एसिंक क्षमताओं के साथ संयोजित करके। इस लेख के दायरे से बाहर है Ruby में एसिंक IO के सभी विवरणों को समझाना। फिर भी, हम इसके कामकाज के आवश्यक भागों को छूएंगे और आप AdHocHTTP के प्रासंगिक मेथड्स के कार्यान्वयन को देख सकते हैं (वही क्लाइंट जो थ्रेडेड समाधान में दिखाई दिया था जिसे हमने अभी खोजा है) यदि आप उत्सुक हैं।
हम अपने फाइबर-संचालित कार्यान्वयन के run मेथड को देखकर शुरू करेंगे:
class FiberPoweredIntegration
# ... एक्सेसर्स और इनिशियलाइज़ेशन ...
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 को विस्तार से देखें:
# ... 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 # सर्वर टाइमआउट होने पर फिर से प्रयास करें।
end
हम पहले अपने क्लाइंट का उपयोग करके एक ऑपरेशन (कनेक्ट, रीड, या राइट) करने का प्रयास करते हैं। दो प्राथमिक परिणाम संभव हैं:
- सफलता: जब ऐसा होता है, तो हम रिटर्न करते हैं।
- हम एक प्रतीक प्राप्त कर सकते हैं: इसका मतलब है कि हमें प्रतीक्षा करनी होगी।
कोई "प्रतीक्षा" कैसे करता है?
- हम अपने सॉकेट को वर्तमान फाइबर के साथ जोड़कर एक तरह का चेकपॉइंट बनाते हैं और इसे इंस्टेंस वेरिएबल
waiting(जो एकHashहै) में जोड़ते हैं। - हम इस जोड़ी को एक संग्रह के अंदर संग्रहीत करते हैं जो रीडिंग या राइटिंग के लिए IO की प्रतीक्षा कर रहा है (हम एक पल में देखेंगे कि यह क्यों महत्वपूर्ण है), क्लाइंट से हमें मिले परिणाम के आधार पर।
- हम वर्तमान फाइबर के निष्पादन को रोकते हैं, जिससे दूसरे को चलने की अनुमति मिलती है। रुका हुआ फाइबर संबंधित नेटवर्क सॉकेट के तैयार होने के बाद किसी बिंदु पर काम फिर से शुरू करने का अवसर प्राप्त करेगा। तब, IO ऑपरेशन को पुनः प्रयास किया जाएगा (और इस बार सफल होगा)।
हर Ruby प्रोग्राम एक फाइबर के अंदर चलता है जो स्वयं एक थ्रेड का हिस्सा है (प्रोसेस के अंदर सब कुछ)। परिणामस्वरूप, जब हम पहली फाइबर बनाते हैं, इसे चलाते हैं, और फिर किसी बिंदु पर यील्ड करते हैं, तो हम प्रोग्राम के केंद्रीय भाग के निष्पादन को फिर से शुरू कर रहे हैं।
अब जब हम समझ गए हैं कि 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 ऑब्जेक्ट्स को लौटाता है जिन्होंने अपना काम पूरा कर लिया है। क्योंकि हमने इन IO ऑब्जेक्ट्स को उन्हें चलाने के लिए जिम्मेदार फाइबर्स के साथ जोड़ा है, उन फाइबर्स को फिर से शुरू करना सरल है।
हम इन चरणों को तब तक दोहराते रहते हैं जब तक सभी रिक्वेस्ट फायर और पूरी नहीं हो जातीं।
ग्रैंड फिनाले: तुलनीय प्रदर्शन, लॉक्स की कोई ज़रूरत नहीं
हमारा handle_response मेथड ठीक वैसा ही है जैसा शुरू में थ्रेड्स का उपयोग करने वाले कोड में उपयोग किया गया था (म्यूटेक्स के बिना वाला संस्करण)। हालांकि, चूंकि हमारे सभी फाइबर्स एक ही थ्रेड के अंदर चलते हैं, हमारे पास कोई डेटा रेस नहीं होगी। जब हम अपना कोड चलाते हैं, तो हमें अपेक्षित परिणाम मिलता है:
{ even: 500, odd: 500 }
आप शायद हर बार जब आप एसिंक IO का लाभ उठाते हैं तो उस फाइबर स्विचिंग व्यवसाय से निपटना नहीं चाहेंगे। सौभाग्य से, कुछ जेम्स इस सभी काम को अमूर्त करते हैं और फाइबर्स के उपयोग को कुछ ऐसा बनाते हैं जिसके बारे में डेवलपर को सोचने की ज़रूरत नहीं है। एक बेहतरीन शुरुआत के रूप में async प्रोजेक्ट देखें।
फाइबर्स तब चमकते हैं जब उच्च स्केलेबिलिटी आवश्यक हो
हालांकि हम छोटे पैमाने के परिदृश्यों में भी डेटा रेस के जोखिम को वस्तुतः समाप्त करने का लाभ उठा सकते हैं, फाइबर्स एक बेहतरीन टूल हैं जब उच्च स्केलेबिलिटी की आवश्यकता होती है। फाइबर्स थ्रेड्स की तुलना में बहुत अधिक हल्के होते हैं। समान उपलब्ध संसाधनों को देखते हुए, थ्रेड्स बनाने से सिस्टम बहुत जल्दी अभिभूत हो जाएगा बजाय फाइबर्स के। इस विषय पर उत्कृष्ट अन्वेषण के लिए, हम Ruby कोर टीम के सैमुअल विलियम्स द्वारा प्रस्तुति The Journey to One Million की सलाह देते हैं।
गिल्ड - Ruby में पैरेलल प्रोग्रामिंग
अब तक हमने Ruby में कंकरेंसी के लिए दो उपयोगी टूल्स देखे हैं। उनमें से कोई भी, हालांकि, शुद्ध गणनाओं के प्रदर्शन को सुधार नहीं सकता। उसके लिए आपको वास्तविक पैरेललिज़्म की आवश्यकता होगी, जो वर्तमान में Ruby में मौजूद नहीं है (यहां हम MRI, डिफ़ॉल्ट कार्यान्वयन पर विचार कर रहे हैं)।
यह Ruby 3 में "गिल्ड्स" नामक एक नई सुविधा के आने के साथ बदल सकता है। विवरण अभी भी अस्पष्ट हैं, लेकिन निम्नलिखित खंडों में हम एक नज़र डालेंगे कि यह कार्य-प्रगति सुविधा Ruby में पैरेललिज़्म की अनुमति देने का वादा कैसे करती है।
गिल्ड्स कैसे काम कर सकते हैं
कंकरेंट/पैरेलल समाधानों को लागू करते समय दर्द का एक महत्वपूर्ण स्रोत शेयर्ड मेमोरी है। थ्रेड्स पर खंड में, हमने पहले ही देखा है कि फिसलना और ऐसा कोड लिखना कितना आसान है जो पहली नज़र में निर्दोष लग सकता है लेकिन वास्तव में सूक्ष्म बग्स रखता है।
कोइची सासादा -- Ruby कोर टीम के सदस्य जो नई गिल्ड सुविधा के विकास का नेतृत्व कर रहे हैं -- एक ऐसे समाधान को डिजाइन करने में कड़ी मेहनत कर रहे हैं जो कई थ्रेड्स के बीच मेमोरी साझा करने के खतरों से सीधे निपटता है। 2018 RubyConf में अपनी प्रस्तुति में, वे बताते हैं कि गिल्ड्स का उपयोग करते समय कोई केवल म्यूटेबल ऑब्जेक्ट्स को साझा नहीं कर पाएगा। मुख्य विचार डेटा रेस को रोकना है केवल अपरिवर्तनीय ऑब्जेक्ट्स को विभिन्न गिल्ड्स के बीच साझा करने की अनुमति देकर।
विशेष डेटा संरचनाएं Ruby में पेश की जाएंगी ताकि गिल्ड्स के बीच शेयर्ड मेमोरी की कुछ मात्रा की अनुमति दी जा सके, लेकिन वास्तव में यह कैसे काम करेगा इसका विवरण अभी पूरी तरह से विकसित नहीं हुआ है। एक API भी होगा जो ऑब्जेक्ट्स को गिल्ड्स के बीच कॉपी या स्थानांतरित करने की अनुमति देगा, साथ ही एक ऑब्जेक्ट को एक अलग गिल्ड में ले जाने के बाद संदर्भित होने से रोकने के लिए एक सुरक्षा उपाय भी होगा।
गिल्ड्स का उपयोग एक सामान्य परिदृश्य का पता लगाने के लिए
ऐसी कई स्थितियां हैं जहां आप गणनाओं को पैरेलल में चलाकर उन्हें तेज करना चाह सकते हैं। कल्पना करें कि हमें एक ही डेटासेट का औसत और माध्यिका की गणना करनी है।
नीचे दिया गया उदाहरण दिखाता है कि हम गिल्ड्स के साथ ऐसा कैसे कर सकते हैं। ध्यान रखें कि यह कोड वर्तमान में काम नहीं करता है और गिल्ड्स जारी होने के बाद भी कभी काम नहीं कर सकता है।
# संख्यात्मक मूल्यों का एक फ्रोजन ऐरे एक अपरिवर्तनीय ऑब्जेक्ट है।
dataset = [88, 43, 37, 85, 84, 38, 13, 84, 17, 87].freeze
# गिल्ड्स का उपयोग करने का ओवरहेड शायद काफी होगा,
# इसलिए बड़ी डेटासेट होने पर / जब बहुत सारे ऑपरेशन कर रहे हों तभी काम को पैरेललाइज़ करना समझ में आएगा।
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 = {}
# हर Ruby प्रोग्राम एक मुख्य गिल्ड के अंदर चलेगा;
# इसलिए, हम अपने प्रोग्राम के मुख्य खंड में संदेश भी प्राप्त कर सकते हैं।
Guild.receive(:mean, :median) do |tag, result|
results[tag] = result
end
इसे संक्षेप में प्रस्तुत करना
कंकरेंसी और पैरेललिज़्म Ruby की मुख्य ताकत नहीं हैं, लेकिन इस विभाग में भी भाषा टूल्स प्रदान करती है जो शायद अधिकांश उपयोग के मामलों से निपटने के लिए पर्याप्त हैं। Ruby 3 आ रहा है और ऐसा लगता है कि गिल्ड प्रिमिटिव की शुरूआत के साथ चीजें काफी बेहतर हो जाएंगी। मेरी राय में, Ruby अभी भी कई स्थितियों में एक बहुत उपयुक्त विकल्प है, और इसका समुदाय स्पष्ट रूप से भाषा को और भी बेहतर बनाने के लिए कड़ी मेहनत कर रहा है। आइए आने वाली चीजों के लिए कान जमीन पर रखें!