81

starting from Rails 4, सब कुछ डिफ़ॉल्ट रूप से लड़ी वातावरण में चलाना होगा। क्या इसका मतलब यह कोड हम लिखने औरसभी जवाहरात उपयोग हम threadsafeकैसे पता चलेगा कि रूबी में थ्रेड-सुरक्षित नहीं है?

तो

होना भी आवश्यक है के सभी है, मैं इस पर कुछ प्रश्न हैं:

  1. क्या नहीं है थ्रेड-सुरक्षित रूबी/रेल में? बनाम क्या रूबी/रेल में धागे की सुरक्षित है?
  2. वहाँ जवाहरात कि है की एक सूची threadsafe या इसके विपरीत हो जाता है?
  3. वहाँ कोड के आम पैटर्न जो threadsafe उदाहरण @result ||= some_method नहीं हैं की सूची है?
  4. ऐसे Hash आदि threadsafe के रूप में गहरे लाल रंग का लैंग कोर में डेटा संरचनाओं हैं?
  5. एमआरआई पर, जहां GVL/GIL का मतलब है कि IO को छोड़कर केवल 1 रूबी धागा चलाया जा सकता है, क्या थ्रेडसेफ परिवर्तन हमें प्रभावित करता है?
+2

आप सुनिश्चित करें कि सभी कोड और जो कुछ कर रहे हैं रत्न थ्रेडसेफ होना चाहिए? रिलीज नोट्स का कहना है कि रेल स्वयं ही थ्रेडसेफ होंगे, न कि इसके साथ उपयोग की जाने वाली हर चीज – enthrops

+0

होनी चाहिए बहु-थ्रेडेड टेस्ट सबसे खराब थ्रेडसेफ जोखिम होगा। जब आपको अपने टेस्ट केस के आस-पास एक पर्यावरण चर के मान को बदलना होता है, तो आप तुरन्त थ्रेडसेफ नहीं होते हैं। आप इसके आसपास कैसे काम करेंगे? और हाँ, सभी रत्नों को थ्रेडसेफ होना चाहिए। –

उत्तर

92

कोर डेटा संरचनाओं में से कोई भी थ्रेड सुरक्षित नहीं है। रूबी के साथ जहाजों के बारे में मुझे पता है कि मानक पुस्तकालय (require 'thread'; q = Queue.new) में कतार कार्यान्वयन है।

एमआरआई जीआईएल धागा सुरक्षा के मुद्दों से हमें बचाओ नहीं है। यह केवल यह सुनिश्चित करें कि दो धागे नहीं सटीक एक ही समय में एक ही समय पर रूबी कोड चला सकते हैं दो अलग अलग CPUs पर जैसे कि, बनाता है। थ्रेड अभी भी आपके कोड में किसी भी बिंदु पर रोका जा सकता है और फिर से शुरू किया जा सकता है। यदि आप @n = 0; 3.times { Thread.start { 100.times { @n += 1 } } } जैसे कोड लिखते हैं उदा। एकाधिक धागे से एक साझा चर को म्यूट कर, बाद में साझा चर का मान निर्धारिती नहीं है। जीआईएल एक कोर सिस्टम की सिमुलेशन कम या ज्यादा है, यह सही समवर्ती कार्यक्रम लिखने के मौलिक मुद्दों को नहीं बदलता है।

भले ही एमआरआई नोड.जेएस की तरह एकल-थ्रेडेड हो, फिर भी आपको समरूपता के बारे में सोचना होगा। वृद्धिशील चर के साथ उदाहरण ठीक काम करेगा, लेकिन आप अभी भी दौड़ की स्थिति प्राप्त कर सकते हैं जहां चीजें गैर-निर्धारक क्रम में होती हैं और एक कॉलबैक क्लॉबर्स दूसरे का नतीजा होता है। सिंगल थ्रेडेड एसिंक्रोनस सिस्टम के बारे में तर्क करना आसान है, लेकिन वे समवर्ती मुद्दों से मुक्त नहीं हैं। बस एकाधिक उपयोगकर्ताओं के साथ एक एप्लिकेशन के बारे में सोचें: यदि दो उपयोगकर्ता एक ही समय में स्टैक ओवरफ़्लो पोस्ट पर एक ही समय में संपादित करते हैं, तो पोस्ट को संपादित करने में कुछ समय व्यतीत करें और फिर सहेजें को हिट करें, जिनके परिवर्तन बाद में किसी तीसरे उपयोगकर्ता द्वारा देखे जाएंगे उसी पोस्ट को पढ़ें?

रूबी में, अधिकांश अन्य समवर्ती रनटाइम्स के रूप में, एक से अधिक ऑपरेशन जो कुछ भी है, वह थ्रेड सुरक्षित नहीं है। @n += 1 धागा सुरक्षित नहीं है, क्योंकि यह कई संचालन है। @n = 1 धागा सुरक्षित है क्योंकि यह एक ऑपरेशन है (यह हुड के नीचे बहुत सारे ऑपरेशन है, और अगर मैं यह वर्णन करने की कोशिश करता हूं कि यह "धागा सुरक्षित" क्यों है, तो अंत में मुझे असंगत परिणाम नहीं मिलेगा कार्य)। @n ||= 1, नहीं है और कोई अन्य शॉर्टेंड ऑपरेशन + असाइनमेंट या तो नहीं है। एक गलती मैंने कई बार बनाई है return unless @started; @started = true लिख रहा है, जो बिल्कुल थ्रेड सुरक्षित नहीं है।

मुझे रूबी के लिए थ्रेड सुरक्षित और गैर थ्रेड सुरक्षित कथन की किसी भी आधिकारिक सूची के बारे में पता नहीं है, लेकिन अंगूठे का एक साधारण नियम है: यदि कोई अभिव्यक्ति केवल एक (साइड-इफेक्ट फ्री) ऑपरेशन करता है तो यह शायद सुरक्षित धागा।उदाहरण के लिए: a + b ठीक है, a = 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 एक्सेसर के माध्यम से लीक। Encapsulation परिप्रेक्ष्य से समस्याग्रस्त होने के अलावा, यह concurrency कीड़े के एक कैन खोलता है। हो सकता है कि कोई उस सरणी को ले लेता है और इसे कहीं और पास करता है, और उस कोड को बदले में लगता है कि अब यह उस सरणी का मालिक है और जो कुछ भी इसके साथ चाहता है वह कर सकता है।

एक और क्लासिक रूबी उदाहरण यह है:

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 निरंतर एक ही मूल्य नहीं है। कॉन्स्टेंट केवल वे संदर्भ में स्थिर होते हैं, वे संदर्भित डेटा संरचनाओं की स्थिरता की गारंटी नहीं देते हैं। बस सोचें कि क्या होगा यदि यह कोड समवर्ती रूप से चलाया गया था।

आप परिवर्तनशील राज्य साझा से बचने है (एक से अधिक थ्रेड द्वारा पहुँचा वस्तुओं में जैसे उदाहरण चर, हैश और सरणियों से अधिक थ्रेड द्वारा पहुँचा तरह डेटा संरचनाओं) धागा सुरक्षा इतनी मेहनत नहीं है। अपने आवेदन के उन हिस्सों को कम करने की कोशिश करें जिन्हें समवर्ती रूप से एक्सेस किया गया हो, और वहां अपने प्रयासों पर ध्यान केंद्रित करें। आईआईआरसी, रेल अनुप्रयोग में, प्रत्येक अनुरोध के लिए एक नया नियंत्रक ऑब्जेक्ट बनाया जाता है, इसलिए यह केवल एक थ्रेड द्वारा उपयोग किया जा रहा है, और यह उस नियंत्रक से आपके द्वारा बनाई गई किसी भी मॉडल ऑब्जेक्ट के लिए जाता है। हालांकि, रेल वैश्विक चर के उपयोग को भी प्रोत्साहित करते हैं (User.find(...) वैश्विक चर User का उपयोग करता है, आप इसे केवल एक वर्ग के रूप में सोच सकते हैं, और यह एक वर्ग है, लेकिन यह वैश्विक चर के लिए नामस्थान भी है), इनमें से कुछ सुरक्षित हैं क्योंकि वे केवल पढ़े जाते हैं, लेकिन कभी-कभी आप इन वैश्विक चरों में चीजें सहेजते हैं क्योंकि यह सुविधाजनक है। जब आप वैश्विक स्तर पर पहुंच योग्य कुछ भी उपयोग करते हैं तो बहुत सावधान रहें।

रेल कुछ समय के लिए थ्रेडेड वातावरण में रेल चलाने के लिए संभव है, इसलिए रेल विशेषज्ञ होने के बावजूद मैं अब तक कहूंगा कि रेल की बात होने पर आपको थ्रेड सुरक्षा के बारे में चिंता करने की ज़रूरत नहीं है अपने आप। आप अभी भी रेल अनुप्रयोगों को बना सकते हैं जो ऊपर बताई गई कुछ चीजों को करके थ्रेड सुरक्षित नहीं हैं। जब यह अन्य रत्न आता है तो मान लें कि वे धागे सुरक्षित नहीं हैं जब तक वे कहते हैं कि वे हैं, और यदि वे कहते हैं कि वे मानते हैं कि वे नहीं हैं, और उनके कोड को देखते हैं (लेकिन सिर्फ इसलिए कि आप देखते हैं कि वे @n ||= 1 जैसी चीजें करते हैं इसका मतलब यह नहीं है कि वे धागे सुरक्षित नहीं हैं, यह सही संदर्भ में करने के लिए एक पूरी तरह से वैध बात है - आपको वैश्विक चर में उत्परिवर्तनीय स्थिति जैसी चीजों की तलाश करनी चाहिए, यह कैसे अपने तरीकों से उत्परिवर्तनीय वस्तुओं को पारित करता है, और विशेष रूप से यह कैसे संभालता है विकल्प हैश)।

अंत में, थ्रेड असुरक्षित होने के कारण एक संक्रमणीय संपत्ति है। कुछ भी जो कुछ ऐसा उपयोग करता है जो थ्रेड सुरक्षित नहीं है, वह स्वयं थ्रेड सुरक्षित नहीं है।

+0

ग्रेट उत्तर। यह मानते हुए कि एक सामान्य रेल ऐप बहु-प्रक्रिया है (जैसे आपने वर्णन किया है, एक ही ऐप तक पहुंचने वाले कई अलग-अलग उपयोगकर्ता), मैं सोच रहा हूं कि समरूपता मॉडल में थ्रेड के * मामूली जोखिम * क्या है ... दूसरे शब्दों में, " खतरनाक "क्या यह थ्रेडेड मोड में चलाना है यदि आप पहले से ही प्रक्रियाओं के माध्यम से कुछ समरूपता से निपट रहे हैं? – gingerlime

+1

@Theo धन्यवाद एक टन। वह निरंतर सामान एक बड़ा बम है। यह भी सुरक्षित प्रक्रिया नहीं है।यदि निरंतर एक अनुरोध में बदल जाता है, तो यह बाद के अनुरोधों को एक धागे में भी बदले गए स्थिरांक को देखने का कारण बनता है। । रूबी स्थिरांक अजीब उथले म्यूटेशन पर – rubish

+3

'STANDARD_OPTIONS = {...} freeze' को बढ़ाने के लिए कर रहे हैं – glebm

9

थियो के जवाब के अलावा, मैं एक जोड़े को समस्या क्षेत्रों की तलाश करने के लिए रेल में विशेष रूप से जोड़ा जाने वाला, यदि आपका स्विचिंग config.threadsafe करने के लिए!

  • कक्षा चर:

    @@i_exist_across_threads

  • ENV:

    ENV['DONT_CHANGE_ME']

  • धागे:

    Thread.start

7

रेल 4 से शुरू, सब कुछ डिफ़ॉल्ट

द्वारा लड़ी वातावरण में चलाने के लिए यह 100% सही नहीं है होगा। थ्रेडसेफ रेल बस डिफ़ॉल्ट रूप से चालू है। यदि आप अभी भी पर एक बहु-प्रक्रिया ऐप सर्वर जैसे यात्री (समुदाय) या यूनिकॉर्न पर तैनात हैं, तो इसमें कोई अंतर नहीं होगा। यह परिवर्तन केवल, आप चिंता अगर आप प्यूमा या यात्री उद्यम> 4.0

की तरह एक मल्टी-थ्रेडेड वातावरण अतीत में पर तैनात यदि आप एक मल्टी-थ्रेडेड अनुप्रयोग सर्वर आप config को चालू करने के लिए किया था पर तैनात करने के लिए करना चाहता था।, जो डिफ़ॉल्ट अब है, क्योंकि सभी यह या तो कोई प्रभाव पड़ा था या भी एक ही प्रक्रिया (Prooflink) में चल रहे एक रेल ऐप पर लागू threadsafe।

लेकिन आप सभी पटरियों 4 streaming लाभ और बहु-क्रम तैनाती के अन्य वास्तविक समय सामान तो शायद आप this लेख की दिलचस्पी हो सकती चाहते हैं यदि। एक रेल ऐप के लिए @Theo उदास के रूप में, आप वास्तव में अनुरोध के दौरान स्थिर स्थिति को विचलित करना चाहते हैं। हालांकि यह पालन करने के लिए एक सरल अभ्यास है, असुविधाजनक रूप से आप इस बारे में सुनिश्चित नहीं कर सकते कि आपको हर मणि मिल जाए। जहां तक ​​मुझे याद है कि जर्बी प्रोजेक्ट से चार्ल्स ओलिवर नटर ने this पॉडकास्ट में इसके बारे में कुछ सुझाव दिए थे।

और तुम एक शुद्ध समवर्ती रूबी प्रोग्रामिंग, जहाँ आप कुछ डेटा संरचनाओं जो एक से अधिक थ्रेड द्वारा पहुँचा रहे हैं की आवश्यकता होगी आप शायद thread_safe मणि उपयोगी मिलेगा लिखने के लिए चाहते हैं, तो

संबंधित मुद्दे