सी ++ में एक सिंगलटन के सुरक्षित आलसी निर्माण थ्रेड

सी ++ में सिंगलटन ऑब्जेक्ट को कार्यान्वित करने का कोई तरीका है जो है:

  1. आलसी रूप से एक थ्रेड सुरक्षित तरीके से निर्मित (दो थ्रेड एक साथ सिंगलटन का पहला उपयोगकर्ता हो सकता है - इसे अभी भी एक बार बनाया जाना चाहिए)।
  2. स्थिर चरों पर पहले से निर्माण नहीं किया जा रहा है (इसलिए सिंगलटन ऑब्जेक्ट स्थिर चर के निर्माण के दौरान उपयोग करने के लिए सुरक्षित है)।

(मुझे अपने सी ++ को काफी अच्छी तरह से नहीं पता है, लेकिन यह मामला है कि किसी भी कोड को निष्पादित करने से पहले अभिन्न और निरंतर स्थैतिक चर प्रारंभ किए जाते हैं (यानि, स्थिर रचनाकारों को निष्पादित करने से पहले भी - उनके मूल्य पहले से ही "प्रारंभिक" प्रोग्राम में हो सकते हैं छवि)? यदि ऐसा है - शायद इसका उपयोग सिंगलटन म्यूटेक्स को लागू करने के लिए किया जा सकता है - जिसका उपयोग वास्तविक सिंगलटन के निर्माण की रक्षा के लिए किया जा सकता है ..)


बढ़िया, ऐसा लगता है कि मेरे पास अब कुछ अच्छे उत्तर हैं (शर्म की बात है कि मैं 2 या 3 को उत्तर होने के रूप में चिह्नित नहीं कर सकता)। दो व्यापक समाधान प्रतीत होते हैं:

  1. एक पीओडी स्थैतिक चर के स्थिर प्रारंभिकरण (गतिशील प्रारंभिकता के विपरीत) का उपयोग करें, और बिल्टिन परमाणु निर्देशों का उपयोग करके अपने स्वयं के म्यूटेक्स को कार्यान्वित करें। यह वह समाधान था जिस पर मैं अपने प्रश्न में संकेत दे रहा था, और मुझे विश्वास है कि मैं पहले से ही जानता था।
  2. कुछ अन्य लाइब्रेरी फ़ंक्शन का उपयोग करें जैसे pthread_once या boost :: call_once । ये मुझे निश्चित रूप से नहीं पता था - और पोस्ट किए गए उत्तरों के लिए बहुत आभारी हूं।
0
ro fr bn

9 उत्तर

आप इसे किसी स्थिर चर के बिना नहीं कर सकते हैं, हालांकि यदि आप एक को सहन करने के इच्छुक हैं, तो आप बूस्ट। पढ़ें । अधिक जानकारी के लिए "वन-टाइम प्रारंभिकरण" अनुभाग पढ़ें।

फिर अपने सिंगलटन एक्सेसर फ़ंक्शन में, ऑब्जेक्ट बनाने के लिए boost :: call_once का उपयोग करें, और इसे वापस करें।

0
जोड़ा
बस मेरी राय है, लेकिन मुझे लगता है कि आपको बूस्ट से सावधान रहना होगा। मैं अपने धागे को सुरक्षित नहीं मानता हूं, भले ही इसे बहुत से थ्रेडिंग संबंधित उप-परियोजनाएं मिलें। (यह दो साल के अलावा दो ऑडिट करने के बाद है, और बग रिपोर्ट को बंद कर दिया गया है "ठीक नहीं होगा")।
जोड़ा लेखक jww, स्रोत

असल में, आप किसी भी सिंक्रनाइज़ेशन (पहले से निर्मित चर) का उपयोग किये बिना, सिंगलटन के सिंक्रनाइज़ किए गए निर्माण के लिए पूछ रहे हैं। सामान्य में, नहीं, यह संभव नहीं है। आपको सिंक्रनाइज़ेशन के लिए कुछ उपलब्ध होना चाहिए।

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

2003 से सी ++ मानक के संशोधन से:

किसी अन्य प्रारंभिक होने से पहले स्थैतिक संग्रहण अवधि (3.7.1) वाले ऑब्जेक्ट शून्य-प्रारंभिक (8.5) होंगे। निरंतर अभिव्यक्ति के साथ शून्य-प्रारंभिकरण और प्रारंभिक रूप से सामूहिक प्रारंभिक कहा जाता है; अन्य सभी प्रारंभिक गतिशील प्रारंभिकरण है। स्थिर अभिव्यक्ति अवधि (5.1 9) के साथ प्रारंभिक स्थिर भंडारण अवधि के साथ पीओडी प्रकारों (3.9) के ऑब्जेक्ट्स को किसी गतिशील प्रारंभिक होने से पहले प्रारंभ किया जाएगा। उसी अनुवाद इकाई में नेमस्पेस स्कोप में परिभाषित स्थिर भंडारण अवधि वाले ऑब्जेक्ट्स और गतिशील रूप से आरंभ किए गए क्रम में उस क्रम में प्रारंभ किया जाएगा जिसमें अनुवाद इकाई में उनकी परिभाषा दिखाई देती है।

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

हालांकि, यह मानता है कि आप जानते हैं कि आप स्थिर प्रारंभिकरण के दौरान इस सिंगलटन का उपयोग करेंगे। यह मानक द्वारा भी गारंटी नहीं है, इसलिए यदि आप पूरी तरह से सुरक्षित होना चाहते हैं, तो एक स्थैतिक-प्रारंभिक म्यूटेक्स का उपयोग करें।

संपादित करें: परमाणु तुलना-और-स्वैप का उपयोग करने के लिए क्रिस का सुझाव निश्चित रूप से काम करेगा। यदि पोर्टेबिलिटी कोई मुद्दा नहीं है (और अतिरिक्त अस्थायी सिंगलेट्स बनाना कोई समस्या नहीं है), तो यह थोड़ा कम ओवरहेड समाधान है।

0
जोड़ा

आप मैट के समाधान का उपयोग कर सकते हैं, लेकिन आपको लॉकिंग के लिए उचित म्यूटेक्स / महत्वपूर्ण अनुभाग का उपयोग करना होगा, और लॉक से पहले और बाद में "pObj == NULL" को चेक करके। बेशक, पीओबीजे को भी स्थिर होना होगा;)। इस मामले में एक म्यूटेक्स अनावश्यक रूप से भारी होगा, आप एक महत्वपूर्ण खंड के साथ बेहतर होगा।

OJ, that doesn't work. As Chris pointed out, that's double-check locking, which is not guaranteed to work in the current C++ standard. See: C++ and the Perils of Double-Checked Locking

संपादित करें: कोई समस्या नहीं, ओजे। यह उन भाषाओं में वास्तव में अच्छा है जहां यह काम करता है। मुझे उम्मीद है कि यह सी ++ 0x में काम करेगा (हालांकि मैं निश्चित नहीं हूं), क्योंकि यह एक सुविधाजनक मुहावरे है।

0
जोड़ा

यहां एक बहुत ही सरल आलसी निर्मित सिंगलटन गेटर है:

Singleton *Singleton::self() {
    static Singleton instance;
    return &instance;
}

यह आलसी है, और अगले सी ++ मानक (सी ++ 0x) के लिए इसे थ्रेड सुरक्षित होना आवश्यक है। असल में, मेरा मानना ​​है कि कम से कम g ++ इसे थ्रेड सुरक्षित तरीके से लागू करता है। तो यदि यह आपका लक्ष्य कंपाइलर या है यदि आप एक कंपाइलर का उपयोग करते हैं जो इसे थ्रेड सुरक्षित तरीके से लागू करता है (शायद नए विजुअल स्टूडियो कंपाइलर्स करते हैं? मुझे नहीं पता), तो यह सब आपको चाहिए ।

http: //www.open-std भी देखें इस विषय पर .org / jtc1 / sc22 / wg21 / दस्तावेज़ / कागजात / 2008 / n2513.html

0
जोड़ा
अच्छा! यह हमारे वर्तमान समाधान की तुलना में एक बहुत अधिक neater होगा। सी ++ 0x कब होगा (या यह सी ++ 1x होना चाहिए) अंततः समाप्त हो जाएगा ..?
जोड़ा लेखक pauldoo, स्रोत
वीएस2015 इस प्रारंभिक पैटर्न के लिए थ्रेड सुरक्षित समर्थन प्रस्तुत करता है।
जोड़ा लेखक Chris Betti, स्रोत

हालांकि इस प्रश्न का उत्तर पहले ही दिया जा चुका है, मुझे लगता है कि उल्लेख करने के लिए कुछ और मुद्दे हैं:

  • यदि आप एक गतिशील आवंटित आवृत्ति के लिए पॉइंटर का उपयोग करते समय सिंगलटन के आलसी-तत्कालता चाहते हैं, तो आपको यह सुनिश्चित करना होगा कि आप इसे सही बिंदु पर साफ़ कर दें।
  • आप मैट के समाधान का उपयोग कर सकते हैं, लेकिन आपको लॉक करने के लिए उचित म्यूटेक्स / महत्वपूर्ण अनुभाग का उपयोग करना होगा, और लॉक से पहले और बाद में "pObj == NULL" की जांच करनी होगी। बेशक, pobb को स्थिर होना भी होगा;) । इस मामले में एक म्यूटेक्स अनावश्यक रूप से भारी होगा, आप बेहतर अनुभाग के साथ बेहतर होंगे।

लेकिन जैसा कि पहले से ही कहा गया है, आप कम से कम एक सिंक्रनाइज़ेशन आदिम का उपयोग किये बिना थ्रेडसेफ आलसी-प्रारंभिकरण की गारंटी नहीं दे सकते।

संपादित करें: यूप डेरेक, आप सही हैं। मेरी गलती। :)

0
जोड़ा

दुर्भाग्यवश, मैट के उत्तर में डबल-चेक लॉकिंग कहा जाता है जो सी / सी ++ मेमोरी मॉडल द्वारा समर्थित नहीं है। (यह जावा 1.5 और बाद में समर्थित है? और मुझे लगता है .NET? मेमोरी मॉडल।) इसका मतलब है कि उस समय के बीच जब pObj == NULL चेक होता है और जब लॉक (mutex) अधिग्रहित किया गया है, pObj पहले से ही किसी अन्य थ्रेड पर असाइन किया जा सकता है। थ्रेड स्विचिंग तब भी होती है जब भी ओएस चाहता है कि प्रोग्राम के "लाइन" के बीच न हो (जिसका अर्थ अधिकांश भाषाओं में पोस्ट-संकलन नहीं है)।

इसके अलावा, जैसा कि मैट स्वीकार करता है, वह ओएस आदिम के बजाए एक लॉक के रूप में int का उपयोग करता है। ऐसा मत करो। उचित ताले को स्मृति बाधा निर्देशों, संभावित रूप से कैश-लाइन फ्लश, और इसी तरह के उपयोग की आवश्यकता होती है; लॉकिंग के लिए अपने ऑपरेटिंग सिस्टम के प्राइमेटिव का उपयोग करें। यह विशेष रूप से महत्वपूर्ण है क्योंकि उपयोग किए जाने वाले प्राइमेटिव अलग-अलग CPU लाइनों के बीच बदल सकते हैं जो आपका ऑपरेटिंग सिस्टम चल रहा है; सीपीयू फू पर क्या काम करता है CPU Foo2 पर काम नहीं कर सकता है। अधिकांश ऑपरेटिंग सिस्टम या तो मूल रूप से पॉज़िक्स धागे (pthreads) का समर्थन करते हैं या उन्हें ओएस थ्रेडिंग पैकेज के लिए एक रैपर के रूप में पेश करते हैं, इसलिए अक्सर उनका उपयोग करके उदाहरणों को चित्रित करना सबसे अच्छा होता है।

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

MySingleton *MySingleton::GetSingleton() {
    if (pObj == NULL) {
        // create a temporary instance of the singleton
        MySingleton *temp = new MySingleton();
        if (OSAtomicCompareAndSwapPtrBarrier(NULL, temp, &pObj) == false) {
            // if the swap didn't take place, delete the temporary instance
            delete temp;
        }
    }

    return pObj;
}

यह केवल तभी काम करता है जब आपके सिंगलटन के कई उदाहरण (एक थ्रेड जो एक साथ GetSingleton() को आमंत्रित करने के लिए होता है), और फिर अतिरिक्त दूर फेंक दें। मैक ओएस एक्स पर OSAtomicCompareAndSwapPtrBarrier फ़ंक्शन प्रदान किया गया है? अधिकांश ऑपरेटिंग सिस्टम एक समान आदिम प्रदान करते हैं? यह जांचता है कि pObj NULL है और केवल इसे वास्तव में temp पर सेट करता है यदि यह है। यह वास्तव में हार्डवेयर समर्थन का उपयोग करता है, सचमुच केवल स्वैप एक बार निष्पादित करता है और बताता है कि यह हुआ है या नहीं।

यदि आपका ओएस इन दो चरम सीमाओं के बीच है, तो इसका लाभ उठाने की एक और सुविधा pthread_once है। यह आपको एक फ़ंक्शन सेट करने देता है जो केवल एक बार चलाया जाता है - मूल रूप से सभी लॉकिंग / बाधा / आदि कर कर। आपके लिए चालबाजी - कोई फर्क नहीं पड़ता कि कितनी बार इसे बुलाया गया है या कितने धागे इसे बुलाया गया है।

0
जोड़ा

जीसीसी के लिए, यह आसान है:

LazyType* GetMyLazyGlobal() {
    static const LazyType* instance = new LazyType();
    return instance;
}

जीसीसी यह सुनिश्चित करेगा कि प्रारंभिक परमाणु है। वीसी ++ के लिए, यह मामला नहीं है । :-(

इस तंत्र के साथ एक बड़ा मुद्दा टेस्टेबिलिटी की कमी है: यदि आपको LazyType को परीक्षणों के बीच एक नए में रीसेट करने की आवश्यकता है, या LazyType * को MockLazyType * में बदलना चाहते हैं, तो आप सक्षम नहीं होंगे। यह देखते हुए, आमतौर पर स्थिर म्यूटेक्स + स्थिर सूचक का उपयोग करना सबसे अच्छा होता है।

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

0
जोड़ा
बाहर निकलने पर क्रैश: हाँ, cxa_finalize क्रैश ... जोड़ा लेखक jww, स्रोत
आप इस पर बिल्कुल सही हैं। लेकिन बेहतर अगर आप बोल्ड "वीसी ++ के लिए यह कैस नहीं है" वाक्यांश। blogs.msdn.com/oldnewthing/archive/2004/03/ 08 / 85901.aspx
जोड़ा लेखक Varuna, स्रोत

मुझे लगता है कि यह मत कहें क्योंकि यह सुरक्षित नहीं है और शायद main() में इस सामग्री को शुरू करने की तुलना में अधिक बार टूट जाएगा, यह लोकप्रिय नहीं होगा।

(और हाँ, मुझे पता है कि इसका सुझाव है कि इसका मतलब है कि आपको वैश्विक वस्तुओं के रचनाकारों में दिलचस्प सामग्री करने का प्रयास नहीं करना चाहिए। यही बात है।)

0
जोड़ा
  1. कमजोर स्मृति मॉडल पर पढ़ें। यह डबल-चेक किए गए ताले और स्पिनलॉक्स तोड़ सकता है। इंटेल मजबूत मेमोरी मॉडल (अभी तक) है, इसलिए इंटेल पर यह आसान है

  2. रजिस्टरों में ऑब्जेक्ट के हिस्सों को कैशिंग से बचने के लिए सावधानी से "अस्थिर" का उपयोग करें, अन्यथा आप ऑब्जेक्ट पॉइंटर को प्रारंभ करेंगे, लेकिन ऑब्जेक्ट स्वयं नहीं, और अन्य थ्रेड क्रैश हो जाएगा

  3. स्थिर चर लोडिंग बनाम स्थिर चर प्रारंभिकरण का क्रम कभी-कभी छोटा नहीं होता है। मैंने मामलों को देखा है जब किसी ऑब्जेक्ट को नष्ट करने के लिए कोड पहले से ही अनलोड किया गया था, इसलिए प्रोग्राम बाहर निकलने पर क्रैश हो गया

  4. ऐसी वस्तुओं को ठीक से नष्ट करना मुश्किल है

सामान्य सिंगलटन में डीबग करने के लिए सही और कठिन करना कठिन होता है। उन्हें पूरी तरह से टालना बेहतर है।

0
जोड़ा