2011-08-26 13 views
10

मैं हाल ही में the following post on the Resharper website पर आया था। यह दोबारा जांच लॉकिंग की चर्चा थी, और निम्न कोड था:डबल-चेक किए गए लॉकिंग में मेमोरी मॉडल गारंटी

public class Foo 
{ 
    private static volatile Foo instance; 
    private static readonly object padlock = new object(); 

    public static Foo GetValue() 
    { 
     if (instance == null) 
     { 
      lock (padlock) 
      { 
       if (instance == null) 
       { 
        instance = new Foo(); 
        instance.Init(); 
       } 
      } 
     } 
     return instance; 
    } 

    private void Init() 
    { 
     ... 
    } 
} 

पद तो दावा

अगर हम मान लेते हैं कि कि Init() intialize करने की विधि है बनाता है फू की स्थिति, तो उपरोक्त कोड स्मृति मॉडल के कारण अपेक्षित कार्य नहीं कर सकता है जो पढ़ने और लिखने के क्रम की गारंटी नहीं देता है। परिणाम के रूप में, Init() को कॉल वास्तव में परिवर्तनीय उदाहरण एक स्थिर स्थिति में होने से पहले हो सकता है।

यहाँ मेरी प्रश्न हैं:

  1. यह था मेरी समझ नेट स्मृति मॉडल (कम से कम 2.0 के बाद से), है कि की आवश्यकता नहीं है कि instancevolatile के रूप में घोषित किया जा के बाद से lock एक प्रदान करेगा पूर्ण स्मृति बाड़। क्या यह मामला नहीं है, या क्या मुझे गलत बताया गया था?

  2. एकाधिक धागे के संबंध में केवल पढ़ने योग्य/पढ़ने योग्य नहीं पढ़ा जाता है? यह मेरी समझ थी कि एक धागे पर, दुष्प्रभाव एक सतत क्रम में होंगे, और lock जगह पर किसी अन्य धागे को अस्वस्थ होने से रोकने से रोक देगा। क्या मैं यहां भी ऑफ-बेस हूं?

+2

आप .NET 2.0 मेमोरी मॉडल के बारे में सही हैं। आपको 'अस्थिर' की आवश्यकता नहीं है (क्योंकि यह शायद ही कभी करता है जो आप चाहते हैं) और एक 'लॉक' वास्तव में एक पूर्ण बाड़ करता है। हालांकि, चूंकि चिबासिटी बताती है, जब थ्रेड-सुरक्षा की बात आती है, तो दौड़ की स्थिति को नजरअंदाज करना बहुत आसान होता है। – Steven

उत्तर

18

उदाहरण के साथ बड़ी समस्या यह है कि पहली अशक्त जांच बंद नहीं किया जाता है तो उदाहरण के रिक्त नहीं हो सकता है, लेकिन से पहले Init बुलाया गया है। इनिट को बुलाए जाने से पहले यह उदाहरण का उपयोग कर धागे का कारण बन सकता है।

सही संस्करण इसलिए किया जाना चाहिए:

public static Foo GetValue() 
{ 
    if (instance == null) 
    { 
     lock (padlock) 
     { 
      if (instance == null) 
      { 
       var foo = new Foo(); 
       foo.Init(); 
       instance = foo; 
      } 
     } 
    } 

    return instance; 
} 
+2

यह बहुत तेज है। मैंने खुद को याद किया। आप पूर्णता के लिए अपने उत्तर में उस कोड का सही संस्करण नहीं जोड़ रहे हैं, है ना? – Steven

+2

@ स्टीवन सही। संपादन के लिए चीयर्स - सराहना की। मेरे फोन से बहुत मुश्किल है! :) –

+3

मुझे इस मोबाइल का जवाब देने के लिए अतिरिक्त अंक प्राप्त करना चाहिए :-) – Steven

1

अगर मैं कोड सही ढंग से पढ़ा है, मुद्दा है: अशक्त

कोलर 1 विधि शुरू होता है, उदाहरण के पाता == सच होना, में प्रवेश करती है लॉक, उदाहरण अभी भी शून्य हो गया है, और उदाहरण बनाता है।

इनिट() को कॉल करने से पहले, कॉलर 1 का धागा निलंबित कर दिया गया है और कॉलर 2 विधि में प्रवेश करता है। कॉलर 2 को उदाहरण नहीं मिल रहा है, और कॉलर 1 इसे प्रारंभ करने से पहले इसका उपयोग करने के लिए चला जाता है।

0

एक ओर यह एक "पूर्ण बाड़" लेकिन क्या उद्धृत करने के लिए बात कर रहा है क्या पर एक "डबल जाँच की लॉकिंग मामले" में "है कि बाड़ के अंदर" ... एक विवरण http://blogs.msdn.com/b/cbrumme/archive/2003/05/17/51445.aspx

को देखने चला जाता है बनाता है इसमें कहा गया है:

However, we have to assume that a series of stores have taken place during construction 
of ‘a’. Those stores can be arbitrarily reordered, including the possibility of delaying 
them until after the publishing store which assigns the new object to ‘a’. At that point, 
there is a small window before the store.release implied by leaving the lock. Inside that 
window, other CPUs can navigate through the reference ‘a’ and see a partially constructed 
instance. 

instance आप उदाहरण से साथ ऊपर वाक्य में बदलें a ...

इसके अतिरिक्त इस http://blogs.msdn.com/b/brada/archive/2004/05/12/130935.aspx की जाँच - यह बताता है कि volatile आपके परिदृश्य में क्या प्राप्त करता है ...

बाड़ और volatile और का एक अच्छा विवरण बेहतर जानकारी मेरी समझ http://csharpindepth.com/Articles/General/Singleton.aspx

12

यह था कि देखते हैं कि कैसे volatile प्रोसेसर आप http://www.albahari.com/threading/part4.aspx और भी अधिक/देखने पर कोड चलाने के आधार पर भी अलग असर पड़ता है नेट मेमोरी मॉडल (कम से कम 2.0 के बाद से) को उस उदाहरण को अस्थिर घोषित करने की आवश्यकता नहीं है, क्योंकि लॉक एक पूर्ण मेमोरी बाड़ प्रदान करेगा। क्या यह मामला नहीं है, या मैं गलत जानकारी दी थी?

यह आवश्यक है। इसका कारण यह है कि आप lock के बाहर instance तक पहुंच रहे हैं। आइए मान लें कि आप volatile को छोड़ दें और इस तरह की प्रारंभिक समस्या को पहले ही तय कर चुके हैं।

public class Foo 
{ 
    private static Foo instance; 
    private static readonly object padlock = new object(); 

    public static Foo GetValue() 
    { 
     if (instance == null) 
     { 
      lock (padlock) 
      { 
       if (instance == null) 
       { 
        var temp = new Foo(); 
        temp.Init(); 
        instance = temp; 
       } 
      } 
     } 
     return instance; 
    } 

    private void Init() { /* ... */ } 
} 

कुछ स्तर पर सी # संकलक, JIT कम्पाइलर, या हार्डवेयर को एक निर्देश अनुक्रम कि दूर temp चर का अनुकूलन और instance चर से पहले Init भाग गया है सौंपा करने के लिए कारण बनता है फेंकना सकता है। वास्तव में, यह कन्स्ट्रक्टर चलाने से पहले instance असाइन कर सकता है। Init विधि इस मुद्दे को स्पॉट करने में आसान बनाती है, लेकिन समस्या अभी भी निर्माता के लिए भी है।

यह वैध अनुकूलन है क्योंकि निर्देश लॉक के भीतर फिर से व्यवस्थित होने के लिए स्वतंत्र हैं। lock मेमोरी बाधा उत्सर्जित करता है, लेकिन केवल Monitor.Enter और Monitor.Exit कॉल पर।

अब, यदि आप volatile को छोड़ देते हैं तो कोड शायद हार्डवेयर और सीएलआई कार्यान्वयन के अधिकांश संयोजनों के साथ काम करेगा। इसका कारण यह है कि x86 हार्डवेयर में एक कड़ा मेमोरी मॉडल है और सीएलआर का माइक्रोसॉफ्ट का कार्यान्वयन भी काफी तंग है। हालांकि, इस विषय पर ईसीएमए विनिर्देश अपेक्षाकृत ढीला है जिसका मतलब है कि सीएलआई का एक और कार्यान्वयन अनुकूलन करने के लिए स्वतंत्र है जो माइक्रोसॉफ्ट वर्तमान में अनदेखा करना चुनता है। आपको कमजोर मॉडल के लिए कोड करना होगा जो एक सीएलआई जिटर हो सकता है, न कि हार्डवेयर जो ज्यादातर लोग ध्यान केंद्रित करते हैं। यही कारण है कि volatile अभी भी आवश्यक है।

एकाधिक धागे के संबंध में केवल पढ़ने योग्य/पढ़ने योग्य नहीं है? यह मेरी समझ थी कि एक धागे पर, प्रभाव लगातार क्रम में होंगे, और यह कि में लॉक किसी भी अन्य धागे को अस्वस्थ होने से रोकने से रोक देगा। क्या मैं यहां भी ऑफ-बेस हूं?

हां। निर्देश पुनर्गठन केवल तब खेलता है जब एक से अधिक धागे एक ही स्मृति स्थान तक पहुंच रहे हैं। यहां तक ​​कि सबसे कमजोर सॉफ़्टवेयर और हार्डवेयर मेमोरी मॉडल किसी भी तरह के अनुकूलन की अनुमति नहीं देंगे जो थ्रेड पर कोड निष्पादित करते समय डेवलपर के इरादे से व्यवहार को बदलता है। अन्यथा, कोई प्रोग्राम सही तरीके से निष्पादित नहीं होगा। मुद्दा यह है कि कैसे अन्य धागे देखते हैं कि उस धागे में क्या चल रहा है। अन्य धागे निष्पादन धागे से अलग व्यवहार को समझ सकते हैं। लेकिन, निष्पादन धागा हमेशा सही व्यवहार को समझता है।

नहीं, lock सभी अपने आप से अन्य धागे को घटनाओं के एक अलग अनुक्रम को समझने से नहीं रोकेंगे। इसका कारण यह है कि निष्पादन थ्रेड lock के अंदर दिए गए निर्देशों को डेवलपर के इरादे से अलग क्रम में निर्देशों को निष्पादित कर सकता है।यह केवल लॉक के प्रवेश और निकास बिंदु पर है कि स्मृति बाधाएं बनाई गई हैं। तो आपके उदाहरण में नए ऑब्जेक्ट का संदर्भ instance को कन्स्ट्रक्टर चलाने से पहले भी सौंपा जा सकता है भले ही आपने lock के साथ उन निर्देशों को लपेट लिया हो।

volatile का उपयोग करना, दूसरे हाथ पर, कैसे lock बर्ताव के अंदर कोड आम ज्ञान के बावजूद विधि की शुरुआत में instance की प्रारंभिक जांच की तुलना में पर बड़ा प्रभाव डाला है। बहुत से लोग सोचते हैं कि प्रमुख मुद्दा यह है कि instance अस्थिर पढ़ने के बिना बासी हो सकती है। यह मामला हो सकता है, लेकिन बड़ा मुद्दा यह है कि lock के अंदर एक अस्थिर लेखन के बिना instance एक उदाहरण का जिक्र कर सकता है जिसके लिए निर्माता अभी तक नहीं चला है। एक अस्थिर लेखन इस समस्या को हल करता है क्योंकि यह संकलक को instance पर लिखने के बाद कन्स्ट्रक्टर कोड को स्थानांतरित करने से रोकता है। यही कारण है कि volatile अभी भी आवश्यक है।

+1

उत्कृष्ट जवाब। –

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