कोर डेटा संरचनाओं में से कोई भी धागा सुरक्षित नहीं है। रूबी के साथ जहाजों के बारे में केवल एक ही मुझे पता है कि मानक पुस्तकालय ( require 'thread'; q = Queue.new
) में कतार कार्यान्वयन है ।
MRI की GIL हमें थ्रेड सेफ्टी के मुद्दों से नहीं बचाती है। यह केवल यह सुनिश्चित करता है कि दो धागे एक ही समय में रूबी कोड नहीं चला सकते हैं , यानी एक ही समय में दो अलग-अलग सीपीयू पर। आपके कोड में किसी भी बिंदु पर थ्रेड्स को रोका जा सकता है और फिर से शुरू किया जा सकता है। यदि आप कोड लिखते हैं @n = 0; 3.times { Thread.start { 100.times { @n += 1 } } }
जैसे कि एक साझा चर को कई थ्रेड से बदलना, बाद में साझा किए गए चर का मान नियतात्मक नहीं है। जीआईएल कमोबेश सिंगल कोर सिस्टम का अनुकरण है, यह सही समवर्ती कार्यक्रमों को लिखने के बुनियादी मुद्दों को नहीं बदलता है।
यहां तक कि अगर MRI को Node.js की तरह सिंगल-थ्रेडेड किया गया था, तब भी आपको कॉन्सेप्ट के बारे में सोचना होगा। वृद्ध चर के साथ उदाहरण ठीक काम करेगा, लेकिन आप अभी भी दौड़ की स्थिति प्राप्त कर सकते हैं जहां चीजें गैर-नियतात्मक क्रम में होती हैं और एक कॉलबैक दूसरे का परिणाम होता है। सिंगल थ्रेडेड एसिंक्रोनस सिस्टम के बारे में तर्क करना आसान है, लेकिन वे समसामयिक मुद्दों से मुक्त नहीं हैं। एक से अधिक उपयोगकर्ताओं के साथ एक एप्लिकेशन के बारे में सोचें: यदि दो उपयोगकर्ता एक ही बार में एक स्टैक ओवरफ्लो पोस्ट पर एडिट हिट करते हैं, तो पोस्ट को संपादित करने में कुछ समय व्यतीत करें और फिर सेव को हिट करें, जिसके परिवर्तन को तीसरे उपयोगकर्ता द्वारा बाद में देखा जाएगा जब वे उसी पोस्ट को पढ़ें?
रूबी में, अधिकांश अन्य समवर्ती रनटाइम के रूप में, जो कुछ भी एक से अधिक ऑपरेशन है, वह थ्रेड सुरक्षित नहीं है। @n += 1
यह थ्रेड सुरक्षित नहीं है, क्योंकि यह कई ऑपरेशन हैं। @n = 1
थ्रेड सुरक्षित है क्योंकि यह एक ऑपरेशन है (यह हुड के तहत बहुत सारे ऑपरेशन हैं, और मैं शायद मुश्किल में पड़ जाऊंगा अगर मैंने यह वर्णन करने की कोशिश की कि यह "थ्रेड सेफ" क्यों है विस्तार से, लेकिन अंत में आपको असाइनमेंट से असंगत परिणाम नहीं मिलेगा )। @n ||= 1
, नहीं है और कोई अन्य शॉर्टहैंड ऑपरेशन + असाइनमेंट नहीं है। एक गलती जो मैंने कई बार की है return unless @started; @started = true
, वह लिख रहा है , जो बिल्कुल भी सुरक्षित नहीं है।
मुझे रूबी के लिए थ्रेड सेफ और नॉन-थ्रेड सेफ स्टेटमेंट्स की किसी भी आधिकारिक सूची का पता नहीं है, लेकिन अंगूठे का एक सरल नियम है: यदि कोई एक्सप्रेशन केवल एक (साइड-इफ़ेक्ट फ्री) ऑपरेशन करता है तो वह थ्रेड सुरक्षित है। उदाहरण के लिए: a + b
ठीक है, ठीक a = b
भी है, और a.foo(b)
ठीक है, अगर विधि foo
साइड-इफ़ेक्ट फ़्री है (चूँकि रूबी में कुछ भी एक विधि कॉल है, तो कई मामलों में असाइनमेंट भी, यह अन्य उदाहरणों के लिए भी जाता है)। इस संदर्भ में साइड-इफेक्ट्स का मतलब उन चीजों से है जो राज्य बदलती हैं। def foo(x); @x = x; end
है न पक्ष प्रभाव मुक्त।
रूबी में थ्रेड सेफ कोड लिखने के बारे में सबसे कठिन चीजों में से एक यह है कि सरणी, हैश और स्ट्रिंग सहित सभी मुख्य डेटा संरचनाएं परस्पर भिन्न हैं। गलती से अपने राज्य के एक टुकड़े को लीक करना बहुत आसान है, और जब वह टुकड़ा उत्परिवर्तित होता है तो वास्तव में खराब हो सकता है। निम्नलिखित कोड पर विचार करें:
class Thing
attr_reader :stuff
def initialize(initial_stuff)
@stuff = initial_stuff
@state_lock = Mutex.new
end
def add(item)
@state_lock.synchronize do
@stuff << item
end
end
end
इस वर्ग का एक उदाहरण थ्रेड्स के बीच साझा किया जा सकता है और वे इसे सुरक्षित रूप से जोड़ सकते हैं, लेकिन एक संगामित बग (यह एकमात्र नहीं है): ऑब्जेक्ट की आंतरिक स्थिति एक्सेसर के माध्यम से लीक होती है stuff
। एन्कैप्सुलेशन के नजरिए से समस्याग्रस्त होने के अलावा, यह कंसीलर वर्म्स का कैन भी खोलता है। हो सकता है कि कोई व्यक्ति उस सरणी को लेता है और उसे कहीं और पास करता है, और यह कोड सोचता है कि यह अब उस सरणी का मालिक है और इसके लिए जो चाहे कर सकता है।
एक और क्लासिक रूबी उदाहरण यह है:
STANDARD_OPTIONS = {:color => 'red', :count => 10}
def find_stuff
@some_service.load_things('stuff', STANDARD_OPTIONS)
end
find_stuff
पहली बार उपयोग किए जाने पर ठीक काम करता है, लेकिन दूसरी बार कुछ और देता है। क्यों? load_things
विधि यह यह करने के लिए पारित कर दिया विकल्पों हैश मालिक है, और करता है सोचने के लिए होता है color = options.delete(:color)
। अब STANDARD_OPTIONS
स्थिरांक का समान मान नहीं है। कॉन्स्टेंट केवल उसी चीज में स्थिर होते हैं जिसका वे संदर्भ लेते हैं, वे उन डेटा संरचनाओं की निरंतरता की गारंटी नहीं देते हैं जिन्हें वे संदर्भित करते हैं। जरा सोचिए अगर यह कोड समवर्ती रूप से चलाया जाता तो क्या होता।
यदि आप साझा किए गए उत्परिवर्तित स्थिति से बचते हैं (उदाहरण के लिए कई थ्रेड द्वारा एक्सेस की गई वस्तुओं में उदाहरण चर, हैश जैसी डेटा संरचनाएं और एकाधिक थ्रेड द्वारा एक्सेस किए गए थ्रेड्स) थ्रेड सुरक्षा इतना कठिन नहीं है। अपने एप्लिकेशन के उन हिस्सों को कम से कम करने की कोशिश करें जो समवर्ती रूप से एक्सेस किए जाते हैं, और वहां अपने प्रयासों को केंद्रित करें। IIRC, एक रेल अनुप्रयोग में, हर अनुरोध के लिए एक नया नियंत्रक ऑब्जेक्ट बनाया जाता है, इसलिए यह केवल एक ही धागे का उपयोग करने वाला होता है, और वही उस नियंत्रक से आपके द्वारा बनाए गए किसी भी मॉडल ऑब्जेक्ट के लिए जाता है। हालांकि, रेल वैश्विक चर (वैश्विक चर User.find(...)
का उपयोग करता है) के उपयोग को भी प्रोत्साहित करती हैUser
, आप इसे केवल एक वर्ग के रूप में सोच सकते हैं, और यह एक वर्ग है, लेकिन यह वैश्विक चर के लिए एक नाम स्थान भी है), इनमें से कुछ सुरक्षित हैं क्योंकि वे केवल पढ़े जाते हैं, लेकिन कभी-कभी आप इन वैश्विक चर में चीजों को सहेजते हैं क्योंकि यह सुविधाजनक है। जब आप ऐसी किसी भी चीज़ का उपयोग करें, जो विश्व स्तर पर सुलभ हो।
यह संभव है कि थ्रेडेड वातावरण में रेल को थोड़ी देर के लिए चलाया जाए, इसलिए बिना रेल विशेषज्ञ के मैं अभी भी इतना ही कहूंगा कि जब आप रेल की बात करेंगे तो आपको थ्रेड सेफ्टी की चिंता नहीं करनी चाहिए। आप अभी भी रेल एप्लिकेशन बना सकते हैं जो ऊपर बताई गई कुछ चीजों को करने से सुरक्षित नहीं हैं। जब यह आता है कि अन्य रत्न मान लेते हैं कि वे थ्रेड सेफ नहीं हैं जब तक कि वे यह नहीं कहते कि वे हैं, और यदि वे कहते हैं कि वे मान रहे हैं कि वे नहीं हैं, और अपने कोड के माध्यम से देखते हैं (लेकिन सिर्फ इसलिए कि आप देखते हैं कि वे चीजें जैसे हैं@n ||= 1
इसका मतलब यह नहीं है कि वे थ्रेड सुरक्षित नहीं हैं, यह सही संदर्भ में करने के लिए एक पूरी तरह से वैध चीज है - आपको इसके बजाय वैश्विक चर में परिवर्तनशील राज्य जैसी चीजों की तलाश करनी चाहिए, यह अपने तरीके से पारित होने योग्य वस्तुओं को कैसे संभालती है, और विशेष रूप से यह कैसे हैंडल विकल्प हैश)।
अंत में, थ्रेड असुरक्षित होना एक सकर्मक गुण है। कुछ भी जो थ्रेड सुरक्षित नहीं है का उपयोग करता है खुद थ्रेड सुरक्षित नहीं है।