क्या मेयर्स का सिंगलटन पैटर्न धागा सुरक्षित है?


145

क्या निम्न कार्यान्वयन, Singleton(मेयर्स सिंग्लटन) थ्रेड के आलसी इनिशियलाइज़ेशन का उपयोग कर सुरक्षित है?

static Singleton& instance()
{
     static Singleton s;
     return s;
}

यदि नहीं, तो इसे थ्रेड को सुरक्षित क्यों और कैसे बनाया जाए?


क्या कोई यह समझा सकता है कि यह धागा सुरक्षित क्यों नहीं है। लिंक में उल्लिखित लेख एक वैकल्पिक कार्यान्वयन (एक पॉइंटर वैरिएबल (स्टैटिक सिंगलटन * pInstance) का उपयोग करके) थ्रेड सुरक्षा पर चर्चा करते हैं।
अंकुर



जवाबों:


168

में सी ++ 11 , यह धागा सुरक्षित है। के अनुसार मानक , §6.7 [stmt.dcl] p4:

यदि नियंत्रण घोषणा को समवर्ती रूप से दर्ज करता है जबकि चर को प्रारंभ किया जा रहा है, तो समवर्ती निष्पादन प्रारंभ के पूरा होने की प्रतीक्षा करेगा

इस सुविधा के लिए GCC और VS सपोर्ट ( डायनामिक इनिशियलाइज़ेशन एंड डिस्ट्रक्शन विद कंसिमेरा , जिसे MSDN पर मैजिक स्टेटिक्स भी कहा जाता है ) इस प्रकार है:

उनकी टिप्पणियों के लिए @Mankarse और @olen_gam को धन्यवाद।


में सी ++ 03 , इस कोड को सुरक्षित थ्रेड नहीं किया गया। "सी ++ और डबल-चेक्ड लॉकिंग के पर्ल्स" नामक मेयर्स का एक लेख है, जिसमें पैटर्न के थ्रेड सुरक्षित कार्यान्वयन पर चर्चा की गई है, और निष्कर्ष यह है कि कम या ज्यादा, (C ++ 03 में) इंस्टेंटिंग विधि के आसपास पूर्ण लॉकिंग मूल रूप से सभी प्लेटफार्मों पर उचित संगामिति सुनिश्चित करने का सबसे सरल तरीका है, जबकि डबल-चेकिंग लॉकिंग पैटर्न वेरिएंट के अधिकांश रूप कुछ आर्किटेक्चर पर दौड़ की स्थिति से पीड़ित हो सकते हैं , जब तक कि निर्देशों को रणनीतिक रूप से मेमोरी बाधाओं के साथ हस्तक्षेप नहीं किया जाता है।


3
आधुनिक C ++ डिजाइन में अलेक्जेंड्रेस्कु द्वारा सिंगलटन पैटर्न (आजीवन और थ्रेडसेफ़्टी) पर एक व्यापक चर्चा भी की गई है। लोकी की साइट देखें: loki-lib.sourceforge.net/index.php?n=Pattern.Singleton
Matthieu M.

1
आप बढ़ावा देने के साथ एक धागा-सुरक्षित सिंगलटन बना सकते हैं :: call_once।
कैशोकॉ

1
दुर्भाग्य से, मानक का यह हिस्सा विजुअल स्टूडियो 2012 सी ++ कंपाइलर में लागू नहीं किया गया है। "सी ++ 11 कोर भाषा की विशेषताएं: कॉनएरेबिलिटी
olen -garn

मानक से स्निपेट निर्माण को संबोधित करता है लेकिन विनाश नहीं। क्या मानक वस्तु को एक धागे पर नष्ट होने से रोकता है जबकि (या उससे पहले) एक और धागा कार्यक्रम समाप्ति पर इसे एक्सेस करने की कोशिश करता है?
स्टूजेसिक

IANA (C ++ भाषा) L लेकिन धारा 3.6.3 [basic.start.term] P2 का सुझाव है कि नष्ट होने के बाद वस्तु तक पहुंचने की कोशिश करके अपरिभाषित व्यवहार को मारना संभव है?
स्टूजैसिक

21

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

static Singleton& instance()
{
    static bool initialized = false;
    static char s[sizeof( Singleton)];

    if (!initialized) {
        initialized = true;

        new( &s) Singleton(); // call placement new on s to construct it
    }

    return (*(reinterpret_cast<Singleton*>( &s)));
}

तो यहाँ एक सरल धागा-सुरक्षित सिंगलटन (विंडोज के लिए) है। यह विंडोज CRITICAL_SECTION ऑब्जेक्ट के लिए एक साधारण वर्ग आवरण का उपयोग करता है ताकि हम कंपाइलर को स्वचालित रूप से CRITICAL_SECTIONपहले main()कहे जाने वाले को इनिशियलाइज़ कर सकें । आदर्श रूप से एक सच्चा RAII क्रिटिकल सेक्शन क्लास का उपयोग किया जाएगा जो कि महत्वपूर्ण सेक्शन होने पर होने वाले अपवादों से निपट सकता है, लेकिन यह इस उत्तर के दायरे से परे है।

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

#include <windows.h>

class CritSection : public CRITICAL_SECTION
{
public:
    CritSection() {
        InitializeCriticalSection( this);
    }

    ~CritSection() {
        DeleteCriticalSection( this);
    }

private:
    // disable copy and assignment of CritSection
    CritSection( CritSection const&);
    CritSection& operator=( CritSection const&);
};


class Singleton
{
public:
    static Singleton& instance();

private:
    // don't allow public construct/destruct
    Singleton();
    ~Singleton();
    // disable copy & assignment
    Singleton( Singleton const&);
    Singleton& operator=( Singleton const&);

    static CritSection instance_lock;
};

CritSection Singleton::instance_lock; // definition for Singleton's lock
                                      //  it's initialized before main() is called


Singleton::Singleton()
{
}


Singleton& Singleton::instance()
{
    // check to see if we need to create the Singleton
    EnterCriticalSection( &instance_lock);
    static Singleton s;
    LeaveCriticalSection( &instance_lock);

    return s;
}

आदमी - कि "बेहतर वैश्विक बनाने के लिए" बहुत बकवास है।

इस कार्यान्वयन में मुख्य कमियां हैं (यदि मैंने कुछ बगों को खिसकने नहीं दिया):

  • यदि new Singleton()फेंकता है, तो लॉक जारी नहीं किया जाएगा। यह एक सरल RAII लॉक ऑब्जेक्ट का उपयोग करके तय किया जा सकता है जो मेरे पास यहां है। यह चीजों को पोर्टेबल बनाने में भी मदद कर सकता है यदि आप लॉक के लिए प्लेटफ़ॉर्म स्वतंत्र आवरण प्रदान करने के लिए बूस्ट जैसी किसी चीज़ का उपयोग करते हैं।
  • यह थ्रेड सुरक्षा की गारंटी देता है जब सिंग्लटन उदाहरण के बाद अनुरोध किया main()जाता है - यदि आप इसे पहले कहते हैं (जैसे स्थिर वस्तु के आरंभीकरण में) चीजें काम नहीं कर सकती हैं क्योंकि CRITICAL_SECTIONहो सकता है कि इसे प्रारंभ न किया जाए।
  • जब भी कोई अनुरोध किया जाता है, तो हर बार एक ताला लगाया जाना चाहिए। जैसा कि मैंने कहा, यह एक साधारण धागा सुरक्षित कार्यान्वयन है। यदि आपको एक बेहतर की जरूरत है (या जानना चाहते हैं कि डबल-चेक लॉक तकनीक जैसी चीजें दोषपूर्ण क्यों हैं), ग्रू के जवाब में जुड़े कागजात देखें ।

1
उह ओह। अगर new Singleton()फेंकता है तो क्या होता है?
sbi

@ याकूब - उचित होने के लिए, पुस्तकालयों के एक समुचित सेट के साथ, गैर-कॉपीबिलिटी और एक उचित RAII लॉक के साथ करने के लिए सभी cruft चले जाएंगे या न्यूनतम होंगे। लेकिन मैं चाहता था कि उदाहरण काफी हद तक आत्म-निहित हो। भले ही सिंगलटन न्यूनतम लाभ के लिए बहुत काम कर रहे हों, लेकिन मैंने उन्हें ग्लोबल्स के उपयोग को प्रबंधित करने में उपयोगी पाया है। वे यह पता लगाने में आसानी करते हैं कि वे कहाँ और कब उपयोग किए गए हैं, बस नामकरण सम्मेलन की तुलना में थोड़ा बेहतर है।
माइकल बूर

@sbi: इस उदाहरण में, यदि new Singleton()थ्रो होता है तो निश्चित रूप से लॉक की समस्या है। एक उचित RAII लॉक क्लास का उपयोग किया जाना चाहिए, lock_guardबूस्ट से कुछ । मैं चाहता था कि उदाहरण अधिक या कम आत्म-निहित हो, और यह पहले से ही थोड़ा सा राक्षस था इसलिए मैंने अपवाद सुरक्षा को छोड़ दिया (लेकिन इसे बाहर कहा गया)। शायद मुझे यह तय करना चाहिए कि कहीं यह कोड कट-एन-पेस्ट न हो जाए।
माइकल बूर

डायनामिक रूप से सिंगलटन को क्यों आवंटित करें? सिर्फ 'इनस्टांस' को 'सिंगलटन :: उदाहरण ()' का स्थिर सदस्य क्यों नहीं बनाया जाए?
मार्टिन यॉर्क

@ मर्टिन - किया। आप सही हैं, यह थोड़ा सरल बनाता है - अगर मैं एक RAII लॉक क्लास का उपयोग करता हूं तो यह और भी बेहतर होगा।
माइकल बूर

10

अगले मानक (खंड 6.7.4) को देखते हुए, यह पता चलता है कि स्थैतिक स्थानीय आरंभीकरण थ्रेड सुरक्षित कैसे है। इसलिए एक बार मानक का खंड व्यापक रूप से लागू हो जाने के बाद, मेयर का सिंगलटन पसंदीदा कार्यान्वयन होगा।

मैं पहले ही कई जवाबों से असहमत हूं। अधिकांश कंपाइलर पहले से ही इस तरह से स्थैतिक आरंभ को लागू करते हैं। एक उल्लेखनीय अपवाद Microsoft Visual Studio है।


6

सही उत्तर आपके कंपाइलर पर निर्भर करता है। यह इसे थ्रेडसेफ़ बनाने का निर्णय ले सकता है; यह "अति सूक्ष्म रूप से" थ्रेडसेफ़ नहीं है।


5

क्या निम्नलिखित कार्यान्वयन [...] धागा सुरक्षित है?

अधिकांश प्लेटफार्मों पर, यह थ्रेड-सुरक्षित नहीं है। (सामान्य डिस्क्लेमर को स्पष्ट करते हुए बताएं कि C ++ मानक को थ्रेड्स के बारे में नहीं पता है, इसलिए, कानूनी रूप से, यह नहीं कहता है कि यह है या नहीं।)

यदि नहीं, तो क्यों […]

कारण यह नहीं है कि कुछ भी एक साथ एक से अधिक थ्रेड को sकंस्ट्रक्टर को निष्पादित करने से रोकता है ।

यह कैसे सुरक्षित धागा बनाने के लिए?

स्कॉट मेयर्स और आंद्रेई अलेक्जेंड्रेस्कु द्वारा "सी ++ और द पेरिल्स ऑफ डबल-चेक्ड लॉकिंग" थ्रेड-सुरक्षित सिंग्लेटन्स के विषय पर एक बहुत अच्छा ग्रंथ है।


2

जैसा कि MSalters ने कहा: यह आपके द्वारा उपयोग किए जाने वाले C ++ कार्यान्वयन पर निर्भर करता है। प्रलेखन की जाँच करें। अन्य प्रश्न के रूप में: "यदि नहीं, तो क्यों?" - C ++ मानक अभी तक थ्रेड्स के बारे में कुछ भी उल्लेख नहीं करता है। लेकिन आगामी C ++ संस्करण थ्रेड्स के बारे में पता है और यह स्पष्ट रूप से बताता है कि स्थैतिक स्थानीय लोगों का आरंभ थ्रेड-सुरक्षित है। यदि दो थ्रेड्स ऐसे फ़ंक्शन को कॉल करते हैं, तो एक थ्रेड एक इनिशियलाइज़ेशन करेगा जबकि दूसरा ब्लॉक करेगा और इसके खत्म होने का इंतजार करेगा।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.