5

इसलिए मैंने अब कई लेखों का दावा किया है कि सी ++ डबल चेक लॉकिंग पर, आमतौर पर कई धागे को आलसी रूप से बनाए गए सिंगलटन को प्रारंभ करने की कोशिश करने से रोकने के लिए उपयोग किया जाता है। सामान्य डबल जाँच की लॉकिंग कोड इस तरह पढ़ता है:डबल चेक किए गए लॉकिंग के लिए इस फ़िक्स के साथ क्या गलत है?

class singleton { 
private: 
    singleton(); // private constructor so users must call instance() 
    static boost::mutex _init_mutex; 

public: 
    static singleton & instance() 
    { 
     static singleton* instance; 

     if(!instance) 
     { 
      boost::mutex::scoped_lock lock(_init_mutex); 

      if(!instance)   
       instance = new singleton; 
     } 

     return *instance; 
    } 
}; 

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

I saw a suggestion थ्रेड स्थानीय बूलियन का उपयोग करने के लिए और instance की बजाय जांचें। कुछ इस तरह:

class singleton { 
private: 
    singleton(); // private constructor so users must call instance() 
    static boost::mutex _init_mutex; 
    static boost::thread_specific_ptr<int> _sync_check; 

public: 
    static singleton & instance() 
    { 
     static singleton* instance; 

     if(!_sync_check.get()) 
     { 
      boost::mutex::scoped_lock lock(_init_mutex); 

      if(!instance)   
       instance = new singleton; 

      // Any non-null value would work, we're really just using it as a 
      // thread specific bool. 
      _sync_check = reinterpret_cast<int*>(1); 
     } 

     return *instance; 
    } 
}; 

इस तरह प्रत्येक थ्रेड पता चल सके कि उदाहरण एक बार बनाया गया है समाप्त होता है, लेकिन उसके बाद बंद हो जाता है, जो कुछ प्रदर्शन हिट जरूरत पर जोर देता, लेकिन अभी भी लगभग इतनी हर कॉल पर ताला लगा के रूप में बुरा नहीं। लेकिन क्या होगा यदि हमने अभी एक स्थानीय स्थैतिक बूल का उपयोग किया ?:

class singleton { 
private: 
    singleton(); // private constructor so users must call instance() 
    static boost::mutex _init_mutex; 

public: 
    static singleton & instance() 
    { 
     static bool sync_check = false; 
     static singleton* instance; 

     if(!sync_check) 
     { 
      boost::mutex::scoped_lock lock(_init_mutex); 

      if(!instance)   
       instance = new singleton; 

      sync_check = true; 
     } 

     return *instance; 
    } 
}; 

यह काम क्यों नहीं करेगा? यहां तक ​​कि सिंक_चेक को एक थ्रेड द्वारा पढ़ा जाना था, जब इसे दूसरे में सौंपा जा रहा है तो कचरा मूल्य अभी भी nonzero और इस प्रकार सच होगा। This Dr. Dobb's article का दावा है कि आपको लॉक करना है क्योंकि आप कभी भी निर्देशक निर्देशों पर कंपाइलर के साथ लड़ाई नहीं जीतेंगे। जो मुझे लगता है कि यह किसी कारण से काम नहीं करना चाहिए, लेकिन मैं यह नहीं समझ सकता कि क्यों। यदि अनुक्रम बिंदुओं की आवश्यकताएं खो जाती हैं क्योंकि डॉ। डॉब के लेख मुझे विश्वास करते हैं, तो मुझे समझ में नहीं आता कि किसी भी कोड को लॉक से पहले लॉक करने के बाद पुन: व्यवस्थित नहीं किया जा सका। जो सी ++ मल्टीथ्रेडिंग टूटी अवधि बना देगा।

मुझे लगता है कि मैं संकलक को विशेष रूप से लॉक से पहले होने के लिए sync_check को पुन: व्यवस्थित करने की अनुमति दे रहा हूं क्योंकि यह एक स्थानीय चर है (और भले ही यह स्थिर है कि हम संदर्भ या सूचक नहीं लौट रहे हैं) - लेकिन फिर यह इसके बजाय इसे स्थिर सदस्य (प्रभावशाली वैश्विक) बनाकर हल किया जा सकता है।

तो क्या यह काम करेगा या नहीं? क्यूं कर?

+2

समस्या यह है कि कन्स्ट्रक्टर को चलाने से पहले वैरिएबल को असाइन किया जा सकता है (या पूरा हो जाता है), ऑब्जेक्ट आवंटित होने से पहले नहीं। – kdgregory

+0

धन्यवाद, सही। मैंने दौड़ की स्थिति पूरी तरह से गलत समझा था। –

+1

हां, आप सही हैं, वर्तमान सी ++ वास्तव में "टूटी अवधि बहुसंख्यक" है। केवल मानक पर विचार करते समय। कंपाइलर विक्रेता सामान्य रूप से इसके आसपास के तरीके प्रदान करते हैं, इसलिए व्यावहारिक परिणाम उस भयानक नहीं हैं। – Suma

उत्तर

5

आपका फिक्स सिंक_चेक को लिखने के बाद से कुछ भी ठीक नहीं करता है और उदाहरण सीपीयू पर ऑर्डर से बाहर किया जा सकता है। उदाहरण के तौर पर कल्पना करें कि उदाहरण के लिए पहले दो कॉल लगभग दो अलग-अलग CPUs पर एक ही समय में होते हैं। पहला धागा ताला हासिल करेगा, सूचक को आरंभ करेगा और उस क्रम में sync_check को सत्य पर सेट करेगा, लेकिन प्रोसेसर स्मृति के लिए स्मृति के क्रम को बदल सकता है। दूसरे सीपीयू पर दूसरे सिरे के लिए sync_check की जांच करना संभव है, देखें कि यह सच है, लेकिन उदाहरण स्मृति में अभी तक लिखा नहीं जा सकता है। विवरण के लिए Lockless Programming Considerations for Xbox 360 and Microsoft Windows देखें।

आपके द्वारा उल्लेख किए गए थ्रेड विशिष्ट सिंक_चेक समाधान को तब काम करना चाहिए (मान लें कि आप अपने पॉइंटर को 0 पर प्रारंभ करें)।

+0

आपकी अंतिम वाक्य के बारे में: हाँ, लेकिन मुझे यकीन नहीं है, लेकिन मुझे लगता है कि thread_specific_ptr आंतरिक रूप से एक mutex का उपयोग करें। तो उस समाधान का उपयोग करने का बिंदु क्या होगा हमेशा म्यूटेक्स को लॉक करना (कोई डबल-लॉकिंग नहीं)? – n1ckp

1

इस बारे में कुछ महान पढ़ने यहाँ (हालांकि यह .net/सी # उन्मुख है) है: http://msdn.microsoft.com/en-us/magazine/cc163715.aspx

क्या यह करने पर निर्भर करता है कि आप सीपीयू है कि यह पुन: व्यवस्थित नहीं कर सकते हैं बताने के लिए सक्षम होना चाहिए है अपने पढ़ता/लिखता है इस परिवर्तनीय पहुंच के लिए (मूल पेंटियम के बाद से, सीपीयू कुछ निर्देशों को फिर से व्यवस्थित कर सकता है अगर ऐसा लगता है कि तर्क अप्रभावित होगा), और यह सुनिश्चित करने की आवश्यकता है कि कैश सुसंगत है (इसके बारे में मत भूलना - हम devs यह दिखाएं कि सभी मेमोरी सिर्फ एक फ्लैट संसाधन है, लेकिन हकीकत में, प्रत्येक सीपीयू कोर में कैश होता है, कुछ अनशेक्षित (एल 1), कुछ कभी-कभी साझा किए जा सकते हैं (एल 2)) - आपका initlization मुख्य रैम पर लिख सकता है, लेकिन एक और कोर कैश में अनियमित मूल्य हो सकता है। यदि आपके पास कोई समवर्ती अर्थशास्त्र नहीं है, तो CPU को पता नहीं हो सकता है कि यह कैश गंदा है।

मुझे सी ++ पक्ष नहीं पता है, लेकिन .NET में, आप चर को इसके उपयोग की सुरक्षा के लिए अस्थिर के रूप में निर्दिष्ट करेंगे (या आप सिस्टम में मेमोरी रीड/राइट बाधा विधियों का उपयोग करेंगे। थ्रेडिंग)।

एक तरफ के रूप में, मैंने इसे .NET 2.0 में पढ़ा है, डबल चेक लॉकिंग को "अस्थिर" चर के बिना काम करने की गारंटी है (वहां किसी भी .net पाठकों के लिए) - जो आपकी सी ++ के साथ आपकी सहायता नहीं करता है कोड।

यदि आप सुरक्षित होना चाहते हैं, तो आपको C#+ को चर के रूप में एक चर को चिह्नित करने के बराबर सी ++ करने की आवश्यकता होगी।

+1

सी ++ चर को अस्थिर घोषित किया जा सकता है, लेकिन मुझे संदेह है कि यह सी # के समान सटीक अर्थशास्त्र है। मुझे यह भी याद रखना याद है कि यह अस्थिरता का दुरुपयोग था लेकिन मुझे याद नहीं है कि क्यों मैं इस बात का न्याय नहीं कर सकता कि लेख कितना तर्कसंगत था। –

+0

विभिन्न भाषाओं में, यह एक दुरुपयोग हो सकता है (सी # में भी दुरुपयोग हो सकता है)। लो-लॉक या लॉक फ्री कोड लिखने के वास्तव में कठिन पहलुओं में से एक मार्गदर्शन में असमानता रही है। मैंने इसके बारे में पढ़ने में समय बिताया है, और ऐसा लगता है कि माइक्रोसॉफ्ट के भीतर भी, कुछ ब्लॉगर्स एक मेमोरी बाड़ की आवश्यकता होने पर एक दूसरे से विरोधाभास करते हैं, और जब आपको अस्थिरता का उपयोग करना चाहिए। यह सुनिश्चित करने के लिए एक मुश्किल समस्या है। – JMarsch

+0

वर्तमान सी ++ (मानक द्वारा परिभाषित) में .NET अस्थिरता के बराबर नहीं है। यह आगामी सी ++ 0x मानक आने वाले क्षेत्रों में से एक है। इस बीच आपको अपने अनुपालन की पेशकश करने की आवश्यकता है (जो विजुअल स्टूडियो में अस्थिर और स्मृति बाड़ का मतलब है)। – Suma

0

"बाद वाला मामला मुहावरे को तोड़ देता है - दो धागे सिंगलटन बनाने के अंत हो सकते हैं।"

लेकिन अगर मैं सही ढंग से कोड को समझता हूं, तो पहला उदाहरण, आप जांचते हैं कि क्या उदाहरण पहले से मौजूद है (एक ही समय में एकाधिक धागे द्वारा निष्पादित किया जा सकता है), अगर यह एक थ्रेड को लॉक करने के लिए नहीं मिलता है और यह बनाता है उदाहरण - उस समय केवल एक धागा सृजन को निष्पादित कर सकता है। अन्य सभी धागे बंद हो जाते हैं और इंतजार करेंगे।

एक बार उदाहरण बन जाता है और म्युटेक्स अनलॉक हो गया है अगले प्रतीक्षा धागा म्युटेक्स लॉक हो जाएगा, लेकिन यह नया उदाहरण बनाने के लिए, क्योंकि जांच विफल हो जाएगा कोशिश नहीं होंगे।

अगली बार इंस्टेंस वैरिएबल चेक किया गया है, तो यह सेट किया जाएगा ताकि कोई थ्रेड नए इंस्टेंस को बनाने का प्रयास न करे।

मैं इस मामले में जहां एक धागा उदाहरण के लिए नया उदाहरण सूचक बताए है, जबकि दूसरे धागा एक ही चर की जाँच करता है के बारे में यकीन नहीं है - लेकिन मेरा मानना ​​है कि इसे सही ढंग से इस मामले में नियंत्रित किया जाएगा।

क्या मुझे यहां कुछ याद आ रही है?

ठीक संचालन की पुनर्व्यवस्था के बारे में है, लेकिन इस मामले में यह तर्क में फेरबदल किया जाएगा तो मैं तो होना ही यह उम्मीद नहीं होता यकीन नहीं है - लेकिन मैं इस विषय पर कोई विशेषज्ञ हूँ।

+0

में परमाणु का उपयोग करें आप सही हैं - मैं वास्तविक दौड़ की स्थिति के बारे में गलत था। समस्या यह है कि एक दूसरा धागा उदाहरण देख सकता है कि गैर-शून्य है और पहले धागे ने इसे बनाने से पहले इसे वापस करने का प्रयास किया है। मैंने अपनी पोस्ट संपादित की है। –

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