2012-10-23 7 views
12

मुझे इस प्रश्न को एक साक्षात्कार में पूछा गया था, और मैं इसका उत्तर नहीं दे सका।परमाणु (थ्रेड-सुरक्षित) और अपवाद-सुरक्षित गहरी प्रति असाइनमेंट ऑपरेटर को कैसे कार्यान्वित करें?

अधिक विशेष रूप से, वर्ग के लिए जो असाइनमेंट ऑपरेटर अंतर्गत आता है इस तरह दिखता है:

class A { 
private: 
    B* pb; 
    C* pc; 
    .... 
public: 
    .... 
} 

कैसे के लिए एक परमाणु (धागा सुरक्षित) और अपवाद-सुरक्षित, गहरी प्रतिलिपि असाइनमेंट ऑपरेटर लागू करने के लिए यह क्लास?

+5

एक धागा सुरक्षित असाइनमेंट ऑपरेटर (सुनिश्चित करें कि आपके ताले रहे आरए II, एक एसटीडी आरए II सूचक धारक में सभी आबंटित स्मृति (यदि आप के बाद उस पर सौंपने जारी करने के लिए) की क्षमता के साथ की दुकान, बनाओ आदि)? उन साक्षात्कारकर्ता वास्तव में इसे – stijn

+1

अपवाद को किस डिग्री से सुरक्षित कर रहे हैं? –

+2

सबकुछ-सुरक्षित-कुछ भी कैसे कार्यान्वित करें? निश्चित रूप से सादे सी पॉइंटर्स का उपयोग करके और ऑब्जेक्ट्स की प्रतियों के साथ गड़बड़ नहीं करते हैं ... वे सुरक्षा के लिए आरएआईआई और स्मार्ट पॉइंटर्स हैं, है ना? – leftaroundabout

उत्तर

12

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

एक अपवाद-सुरक्षित असाइनमेंट ऑपरेटर बनाना सीधे कॉपी कन्स्ट्रक्टर का लाभ उठाने वाला है। तर्क की नकल की है और सदस्यों को लगा दिया जाता था:

A_unlocked& A_unlocked::operator= (A_unlocked const& other) { 
    A_unlocked(other).swap(*this); 
    return *this; 
} 
बेशक

, इसका मतलब है कि एक उपयुक्त प्रतिलिपि निर्माता और एक swap() सदस्य लागू किया जाता है। कई संसाधनों के आवंटन से निपटने, उदाहरण के लिए, ढेर पर आवंटित कई ऑब्जेक्ट्स, प्रत्येक ऑब्जेक्ट के लिए उपयुक्त संसाधन हैंडलर द्वारा किया जा सकता है। संसाधन हैंडलर के उपयोग के बिना अपवाद फेंकने के मामले में सभी संसाधनों को सही ढंग से साफ करने के लिए यह बहुत गन्दा हो जाता है। ढेर आवंटित स्मृति को बनाए रखने के उद्देश्य से std::unique_ptr<T> (या std::auto_ptr<T> यदि आप सी ++ 2011 का उपयोग नहीं कर सकते हैं) एक उपयुक्त विकल्प है। नीचे दिया गया कोड केवल वस्तुओं की ओर इशारा करता है हालांकि उन्हें सदस्यों को बनाने के बजाय ढेर पर वस्तुओं को आवंटित करने में बहुत अधिक बिंदु नहीं है।

class A_unlocked { 
private: 
    std::unique_ptr<B> pb; 
    std::unique_ptr<C> pc; 
    // ... 
public: 
    A_unlocked(/*...*/); 
    A_unlocked(A_unlocked const& other); 
    A_unlocked& operator= (A_unlocked const& other); 
    void swap(A_unlocked& other); 
    // ... 
}; 

A_unlocked::A_unlocked(A_unlocked const& other) 
    : pb(new B(*other.pb)) 
    , pc(new C(*other.pc)) 
{ 
} 
void A_unlocked::swap(A_unlocked& other) { 
    using std::swap; 
    swap(this->pb, other.pb); 
    swap(this->pc, other.pc); 
} 

धागे की सुरक्षा बिट यह आवश्यक है पता चला है कि कोई अन्य धागे से खिलवाड़ कर रहा है के लिए: एक वास्तविक उदाहरण में वस्तुओं शायद सही प्रकार का ऑब्जेक्ट बनाने के लिए एक clone() विधि या किसी अन्य तरीके लागू करेगा कॉपी ऑब्जेक्ट। ऐसा करने का तरीका एक म्यूटेक्स का उपयोग कर रहा है। ,

class A { 
private: 
    mutable std::mutex d_mutex; 
    A_unlocked   d_data; 
public: 
    A(/*...*/); 
    A(A const& other); 
    A& operator= (A const& other); 
    // ... 
}; 

ध्यान दें कि A के सभी सदस्यों को कुछ संगामिति संरक्षण करने की आवश्यकता होगी प्रकार A की वस्तुओं बाहरी लॉकिंग के बिना इस्तेमाल किया जा के लिए होती हैं, तो: जो है, class A कुछ इस तरह लग रहा है।चूंकि म्यूटेक्स समवर्ती पहुंच के खिलाफ सुरक्षा के लिए उपयोग किया जाता है, वास्तव में वस्तु के राज्य का हिस्सा नहीं है, लेकिन ऑब्जेक्ट के राज्य को पढ़ने के दौरान भी बदला जाना चाहिए, इसे mutable बनाया गया है। जगह में इस के साथ, एक प्रति निर्माता बनाने सीधे आगे है:

A::A(A const& other) 
    : d_data((std::unique_lock<std::mutex>(other.d_mutex), other.d_data)) { 
} 

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

असाइनमेंट ऑपरेटर का मूल तर्क भी अपने असाइनमेंट ऑपरेटर का उपयोग करके आधार पर प्रतिनिधि करता है। मुश्किल बात यह है कि दो म्यूटेक्स हैं जिन्हें लॉक करने की आवश्यकता है: ऑब्जेक्ट के लिए एक और तर्क के लिए एक। चूंकि एक और थ्रेड दो वस्तुओं को विपरीत तरीके से असाइन कर सकता है, इसलिए मृत-लॉक की संभावना है। सुविधाजनक रूप से, मानक सी ++ लाइब्रेरी std::lock() एल्गोरिदम प्रदान करती है जो ताले को उपयुक्त तरीके से प्राप्त करती है जो मृत-ताले से बचाती है। एक तरीका यह एल्गोरिथ्म का उपयोग करने के खुला std::unique_lock<std::mutex> वस्तुओं में पारित करने के लिए है, आवश्यकता होने पर प्रत्येक म्युटेक्स के लिए एक का अधिग्रहण किया जाना है:

A& A::operator= (A const& other) { 
    if (this != &other) { 
     std::unique_lock<std::mutex> guard_this(this->d_mutex, std::defer_lock); 
     std::unique_lock<std::mutex> guard_other(other.d_mutex, std::defer_lock); 
     std::lock(guard_this, guard_other); 

     *this->d_data = other.d_data; 
    } 
    return *this; 
} 

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

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

+0

इससे थ्रेडसेफ को और अधिक नहीं लेना पड़ेगा (थ्रेड ए प्रतियां, थ्रेड बी संशोधित)? क्या यह दोनों को लॉक करने और एक विशेष मामला बनाने की कोशिश नहीं करना चाहिए यदि स्रोत और गंतव्य समान है और फिर केवल एक लॉक प्राप्त करें? इसके अलावा आत्म-संकेत कैसे समझते हैं, क्या कोई उदाहरण है? – ted

+2

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

+0

यह ठोस लग रहा है। मैं केवल जोड़ना होगा के कार्यान्वयन पर निर्भर करता है ने कहा कि प्रति ctor, इस समाधान (शायद सबसे/सभी व्यावहारिक समाधान?) पर सबसे अच्छा [मजबूत अपवाद गारंटी] की पेशकश कर सकते (http://en.wikipedia.org/wiki/Exception_guarantees) चूंकि यह शायद बी, सी की प्रतियों को गतिशील रूप से आवंटित करेगा। साथ ही, यह सावधान रहना चाहिए कि अपवाद के मामले में म्यूटेक्स को बंद न करें, अगर यह वास्तव में मजबूत गारंटी बनाना चाहता है। – WeirdlyCheezy

0

अपवाद-सुरक्षित? प्राइमेटिव्स पर ऑपरेशन फेंक नहीं देते हैं ताकि हम इसे मुफ्त में प्राप्त कर सकें।

परमाणु? सबसे आसान 2x sizeof(void*) के लिए परमाणु स्वैप होगा - मुझे विश्वास है कि अधिकांश प्लेटफॉर्म इसे पेश करते हैं। यदि वे नहीं करते हैं, तो आपको या तो लॉक का उपयोग करना होगा, या लॉकलेस एल्गोरिदम हैं जो काम कर सकते हैं।

संपादित करें: दीप कॉपी, हुह? आपको ए और बी को नए अस्थायी स्मार्ट पॉइंटर्स में कॉपी करना होगा, फिर परमाणु रूप से उन्हें स्वैप करें।

+0

क्या आप कोडों की कई पंक्तियां जोड़ना चाहते हैं? –

+4

नहीं। मैं ऐसा क्यों करूं? – Puppy

4

सबसे पहले, आपको समझना होगा कि कोई ऑपरेशन थ्रेड सुरक्षित नहीं है, बल्कि किसी दिए गए संसाधन पर सभी ऑपरेशन पारस्परिक रूप से थ्रेड सुरक्षित हो सकते हैं। तो हमें गैर-असाइनमेंट ऑपरेटर कोड के व्यवहार पर चर्चा करनी चाहिए।

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

एक दूसरा मार्ग ए और उसके सभी डेटा पर एक मोनोलिथिक लॉक (म्यूटेक्स या रीडर-लेखक) बनाना होगा। उस स्थिति में, आपको या तो रेस-फ्री ऑपरेटर = बनाने के लिए ए (या इसी तरह की तकनीक) के उदाहरणों के लिए ताले पर म्यूटक्स ऑर्डरिंग की आवश्यकता होती है, या संभावित रूप से आश्चर्यजनक दौड़ की स्थिति की संभावना को स्वीकार करते हैं और कॉपी-स्वैप मुहावरे डाइटमार का उल्लेख करते हैं। (कॉपी-मूव भी स्वीकार्य है) (लॉक-कॉपीकॉनस्ट्रक्चर में स्पष्ट दौड़ की स्थिति, लॉक-स्वैप असाइनमेंट ऑपरेटर =: थ्रेड 1 एक्स = वाई थ्रेड 2 करता है वाई। फ्लैग = सत्य, एक्स.फ्लैग = सत्य। बाद में राज्य: एक्स .flag गलत है। भले ही थ्रेड 2 पूरे असाइनमेंट पर एक्स और वाई दोनों को लॉक करता है, यह हो सकता है। यह कई प्रोग्रामर को आश्चर्यचकित करेगा।)

पहले मामले में, गैर-असाइनमेंट कोड को कॉपी-ऑन- अर्थशास्त्र लिखें। दूसरे मामले में, गैर-असाइनमेंट कोड को मोनोलिथिक लॉक का पालन करना होता है।

अपवाद सुरक्षा का सवाल है, अगर आप अपने प्रति निर्माता अनुमान के रूप में अपने लॉक कोड, लॉक-प्रति-लॉक-स्वैप एक (दूसरा) अपवाद सुरक्षित है, अपवाद सुरक्षित है। पहले के लिए, जब तक आपका संदर्भ गिनती हो, क्लोन क्लोन और डेटा संशोधन कोड अपवाद सुरक्षित है आप अच्छे हैं: ऑपरेटर = कोड किसी भी मामले में सुंदर मस्तिष्क मृत है।

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