समझना std :: atomic :: Compar_exchange_weak () C ++ 11 में


86
bool compare_exchange_weak (T& expected, T val, ..);

compare_exchange_weak()C ++ 11 में प्रदान किए गए तुलना-विनिमय प्राइमेटिक्स में से एक है। यह इस अर्थ में कमजोर है कि यह तब भी गलत है जब वस्तु का मूल्य बराबर हो expected। इस की वजह से है नकली विफलता कुछ प्लेटफॉर्म जहां (x86 पर के रूप में एक की जगह) निर्देश के अनुक्रम इसे लागू करने के लिए उपयोग किया जाता है। ऐसे प्लेटफ़ॉर्म पर, संदर्भ स्विच, एक ही पते (या कैश लाइन) को किसी अन्य थ्रेड द्वारा पुनः लोड करना, आदिम विफल हो सकते हैं। ऐसा इसलिए है spuriousक्योंकि यह ऑब्जेक्ट का मान नहीं है (बराबर नहीं है expected) जो ऑपरेशन को विफल करता है। इसके बजाय, यह समय के मुद्दों की तरह है।

लेकिन क्या पहेलियाँ मुझे C ++ 11 मानक (आईएसओ / आईईसी 14882) में कहा जाता है,

29.6.5 .. सहज विफलता का एक परिणाम यह है कि कमजोर तुलना और विनिमय के लगभग सभी उपयोग एक लूप में होंगे।

लगभग सभी उपयोगों में इसे लूप में क्यों रखना पड़ता है ? क्या इसका मतलब यह है कि जब हम असफलता के कारण असफल हो जाते हैं, तो हम लूप करेंगे? यदि ऐसा है, तो हम compare_exchange_weak()स्वयं को लूप लिखने और लिखने से क्यों परेशान करते हैं? हम बस उपयोग कर सकते हैं compare_exchange_strong()जो मुझे लगता है कि हमारे लिए सहज विफलताओं से छुटकारा पाना चाहिए। के आम उपयोग के मामले क्या हैं compare_exchange_weak()?

एक और सवाल संबंधित। अपनी पुस्तक "C ++ कंसीडर इन एक्शन" में एंथनी कहते हैं,

//Because compare_exchange_weak() can fail spuriously, it must typically
//be used in a loop:

bool expected=false;
extern atomic<bool> b; // set somewhere else
while(!b.compare_exchange_weak(expected,true) && !expected);

//In this case, you keep looping as long as expected is still false,
//indicating that the compare_exchange_weak() call failed spuriously.

!expectedलूप स्थिति में क्यों है ? क्या यह रोकने के लिए कि सभी धागे भूखे रह सकते हैं और कुछ समय के लिए प्रगति नहीं कर सकते हैं?

संपादित करें: (एक अंतिम प्रश्न)

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

// use LL/SC (or CAS on x86) and ignore/loop on spurious failures
while (!compare_exchange_weak(..))
{ .. }

// use LL/SC (or CAS on x86) and ignore/loop on spurious failures
while (!compare_exchange_strong(..)) 
{ .. }

मैं w / इस अंतिम प्रश्न पर आता हूं आप सभी लोग उल्लेख करते हैं कि लूप के अंदर शायद एक प्रदर्शन अंतर है। इसका उल्लेख C ++ 11 मानक (ISO / IEC 14882) द्वारा भी किया गया है:

जब एक तुलना और विनिमय एक लूप में होता है, तो कमजोर संस्करण कुछ प्लेटफार्मों पर बेहतर प्रदर्शन देगा।

लेकिन जैसा कि ऊपर विश्लेषण किया गया है, एक लूप में दो संस्करणों को समान / समान प्रदर्शन देना चाहिए। मुझे क्या बात याद आती है?


4
डब्ल्यू / आर / टी पहला सवाल है, कई मामलों में आपको वैसे भी लूप करने की आवश्यकता है (चाहे आप मजबूत या कमजोर संस्करण का उपयोग करें), और कमजोर संस्करण में मजबूत से बेहतर प्रदर्शन हो सकता है।
टीसी

2
दोनों कमजोर और मजबूत कैस को "एलएल / एससी का उपयोग करके" लागू किया जाता है, उसी तरह जैसे कि बुलबुला सॉर्ट और क्विकॉर्ट दोनों को "स्वेट का उपयोग करके" लागू किया जाता है; अर्थात्, इस अर्थ में कि वह कार्य करने के लिए उपयोग किया जाने वाला आदिम संचालन है। वे एलएल / एससी के आसपास जो लपेटते हैं वह बहुत अलग है। कमजोर CAS सिर्फ LL / SC है। मजबूत कैस अन्य सामानों के समूह के साथ LL / SC है।
स्नेफेल

1
forum.manning.com/posts/list/33062.page क्या यह मदद करता है?
Tu Xiaomi

@TuXiaomi उस लिंक में उत्तर के साथ, मैं यह नहीं देख सकता कि "कमजोर संस्करण कुछ प्लेटफार्मों पर बेहतर प्रदर्शन क्यों देगा" जैसा कि मानक में कहा गया है।
Deqing

@ दूसरों पर, अन्य प्रोसेसर या थ्रेड्स के अवरोधों या क्रियाओं के कारण, दूसरों की तुलना तुलनीय रूप से की जा सकती है। उन प्लेटफार्मों पर, Compar_exchange_strong प्रभावी रूप से तुलना_exchange_weak पर एक लूप है - यदि यह सहज रूप से विफल हो गया तो यह फिर से लूप हो जाता है। क्या इसने सहायता की? शायद मैं गलत हूं
Tu Xiaomi

जवाबों:


72

एक पाश में विनिमय क्यों कर रहे हैं?

आमतौर पर, आप चाहते हैं कि आप आगे बढ़ने से पहले अपना काम पूरा करें, इस प्रकार, आप compare_exchange_weakएक लूप में डालते हैं ताकि यह तब तक विनिमय करने की कोशिश करे जब तक कि यह सफल न हो जाए (यानी, रिटर्न true)।

ध्यान दें कि compare_exchange_strongअक्सर एक लूप में भी उपयोग किया जाता है। यह असफलता के कारण विफल नहीं होता है, लेकिन समवर्ती लेखन के कारण विफल होता है।

के weakबजाय का उपयोग क्यों करें strong?

काफी आसान: अक्सर असफलता नहीं होती है, इसलिए यह कोई बड़ा प्रदर्शन नहीं है। विवशता में, इस तरह की विफलता को सहन करना कुछ प्लेटफार्मों पर weakसंस्करण की तुलना में अधिक कुशल कार्यान्वयन की अनुमति देता strongहै: strongहमेशा असफलता की जांच करनी चाहिए और इसे मुखौटा बनाना चाहिए। ये कीमती है।

इस प्रकार, weakइसका उपयोग किया जाता है क्योंकि यह strongकुछ प्लेटफार्मों की तुलना में बहुत तेज है

आपको कब weakऔर कैसे उपयोग करना चाहिए strong?

संदर्भ में कहा गया है संकेत जब उपयोग करने के लिए weakजब उपयोग करने के लिए और strong:

जब एक तुलना और विनिमय एक लूप में होता है, तो कमजोर संस्करण कुछ प्लेटफार्मों पर बेहतर प्रदर्शन देगा। जब एक कमजोर तुलना और विनिमय के लिए एक लूप की आवश्यकता होगी और एक मजबूत नहीं होगा, तो मजबूत एक बेहतर होगा।

तो यह उत्तर याद रखने में काफी सरल प्रतीत होता है: यदि आपको केवल असफलता के कारण पाश का परिचय देना होगा, तो ऐसा न करें; का उपयोग करें strong। यदि आपके पास वैसे भी एक लूप है, तो उपयोग करें weak

!expectedउदाहरण में क्यों है?

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

यह केवल एक फास्ट ट्रैक है जब एक और धागा लिखता है true: फिर हम trueफिर से लिखने की कोशिश करने के बजाय गर्भपात करते हैं।

आपके आखिरी सवाल के बारे में

लेकिन जैसा कि ऊपर विश्लेषण किया गया है, एक लूप में दो संस्करणों को समान / समान प्रदर्शन देना चाहिए। मुझे क्या बात याद आती है?

से विकिपीडिया :

एलएल / एससी के वास्तविक कार्यान्वयन हमेशा सफल नहीं होते हैं यदि प्रश्न में स्मृति स्थान के समवर्ती अपडेट नहीं होते हैं। दो ऑपरेशनों के बीच कोई भी असाधारण घटना, जैसे कि एक संदर्भ स्विच, एक अन्य लोड-लिंक, या यहां तक ​​कि (कई प्लेटफार्मों पर) एक और लोड या स्टोर ऑपरेशन, स्टोर-सशर्त को सहज रूप से विफल करने का कारण होगा। यदि मेमोरी बस पर कोई अपडेट प्रसारित किया जाता है तो पुराने कार्यान्वयन विफल हो जाएंगे।

इसलिए, एलएल / एससी संदर्भ स्विच पर सहज रूप से विफल हो जाएगा, उदाहरण के लिए। अब, मजबूत संस्करण अपने "छोटे लूप" को लाएगा, ताकि उस असफल विफलता का पता लगाया जा सके और फिर से कोशिश करके उसे मुखौटा बनाया जा सके। ध्यान दें कि यह स्वयं का लूप भी सामान्य कैस लूप की तुलना में अधिक जटिल है, क्योंकि यह समवर्ती विफलता (और इसे मुखौटा) और समवर्ती पहुंच के कारण विफलता के बीच अंतर करना चाहिए (जिसके परिणामस्वरूप मूल्य के साथ वापसी होती है false)। कमजोर संस्करण में ऐसा लूप नहीं है।

चूंकि आप दोनों उदाहरणों में एक स्पष्ट लूप प्रदान करते हैं, इसलिए मजबूत संस्करण के लिए छोटे लूप का होना जरूरी नहीं है। नतीजतन, strongसंस्करण के साथ उदाहरण में , विफलता की जांच दो बार की जाती है; एक बार compare_exchange_strong(जो कि अधिक जटिल है क्योंकि यह सहज विफलता और समवर्ती लहजे को अलग करना चाहिए) और एक बार आपके लूप द्वारा। यह महंगी जाँच अनावश्यक है और यही कारण है कि weakयहाँ तेजी से होगा।

यह भी ध्यान दें कि आपका तर्क (LL / SC) इसे लागू करने की सिर्फ एक संभावना है। ऐसे और भी प्लेटफ़ॉर्म हैं जिनमें अलग-अलग निर्देश सेट हैं। इसके अलावा (और अधिक महत्वपूर्ण रूप से), ध्यान दें कि सभी संभव डेटा प्रकारों केstd::atomic लिए सभी संचालन का समर्थन करना चाहिए , इसलिए भले ही आप दस मिलियन बाइट संरचना की घोषणा करते हैं, आप इस पर उपयोग कर सकते हैं । यहां तक ​​कि जब सीपीयू में कैस होता है, तो आप दस मिलियन बाइट्स कैस नहीं कर सकते हैं, इसलिए कंपाइलर अन्य निर्देश उत्पन्न करेगा (संभवत: लॉक अधिग्रहण, इसके बाद गैर-परमाणु तुलना और स्वैप, जिसके बाद लॉक रिलीज होता है)। अब, दस मिलियन बाइट्स स्वैप करते समय कितनी चीजें हो सकती हैं, इसके बारे में सोचें। तो जबकि 8 बाइट एक्सचेंजों के लिए एक बहुत बड़ी त्रुटि हो सकती है, यह इस मामले में अधिक सामान्य हो सकता है।compare_exchange

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


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

3
@Voo: अपडेट किया गया उत्तर। अब संदर्भ से संकेत शामिल किए गए हैं। एक एल्गोरिथ्म हो सकता है जो एक भेद करता है। उदाहरण के लिए, "एक को इसे अपडेट करना चाहिए" शब्दार्थ पर विचार करें: कुछ अपडेट करना बिल्कुल एक बार किया जाना चाहिए, इसलिए एक बार जब हम समवर्ती लेखन के कारण असफल हो जाते हैं, तो हम जानते हैं कि किसी और ने इसे किया था और हम गर्भपात कर सकते हैं। अगर हम असफलता के कारण असफल होते हैं, तो किसी ने भी इसे अपडेट नहीं किया है, इसलिए हमें पीछे हटना चाहिए।
gexicide

8
" उदाहरण में अपेक्षित क्यों है? इसे शुद्धता की आवश्यकता नहीं है। इसे स्वीकार करने से समान शब्दार्थ मिलेगा।" - ऐसा नहीं है ... अगर कहें कि पहला एक्सचेंज विफल हो जाता है क्योंकि यह bपहले से ही मिल जाता है true, तब - expectedअभी true- इसके बिना && !expectedलूप्स और दूसरा (मूर्खतापूर्ण) विनिमय करने की कोशिश करता है trueऔर trueजो अच्छी तरह से "सफल" हो सकता है "तुच्छ रूप से whileलूप से टूट जाता है , लेकिन प्रदर्शित कर सकता है" सार्थक रूप से भिन्न व्यवहार यदि bइस बीच वापस बदल गया था false, तो उस स्थिति में लूप जारी रहेगा और अंततः टूटने से पहले फिर से सेट हो सकता हैb true
टोनी डेलरॉय

@TonyD: सही है कि मुझे स्पष्ट करना चाहिए।
gexicide

क्षमा करें दोस्तों, मैंने एक और अंतिम प्रश्न जोड़ा;)
एरिक जेड

17

लगभग सभी उपयोगों में इसे लूप में क्यों रखना पड़ता है ?

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

क्या इसका मतलब यह है कि जब हम असफलता के कारण असफल हो जाते हैं, तो हम लूप करेंगे?

हाँ।

यदि ऐसा है, तो हम compare_exchange_weak()स्वयं को लूप लिखने और लिखने से क्यों परेशान करते हैं? हम सिर्फ तुलना_एक्सचेंज_स्ट्रॉन्ग () का उपयोग कर सकते हैं, जो मुझे लगता है कि हमारे लिए सहज विफलताओं से छुटकारा पाना चाहिए। तुलना_एक्सचेंज_वॉक () के आम उपयोग के मामले क्या हैं?

कुछ आर्किटेक्चर compare_exchange_weakपर अधिक कुशल है, और सहज विफलताओं को काफी असामान्य होना चाहिए, इसलिए कमजोर रूप और लूप का उपयोग करके अधिक कुशल एल्गोरिदम लिखना संभव हो सकता है।

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

!expectedलूप स्थिति में क्यों है ?

मान trueकिसी अन्य थ्रेड द्वारा सेट किया जा सकता था , इसलिए आप इसे सेट करने के लिए लूपिंग रखना नहीं चाहते हैं।

संपादित करें:

लेकिन जैसा कि ऊपर विश्लेषण किया गया है, एक लूप में दो संस्करणों को समान / समान प्रदर्शन देना चाहिए। मुझे क्या बात याद आती है?

निश्चित रूप से यह स्पष्ट है कि उन प्लेटफ़ॉर्मों पर जहाँ स्प्यूरियस फेल होना संभव compare_exchange_strongहै, स्पुरियस फ़ेल्योर और रीट्री के लिए जाँच करना अधिक जटिल होना चाहिए।

कमजोर रूप सिर्फ सहज विफलता पर लौटता है, यह पुन: प्रयास नहीं करता है।


2
+1 सभी गणनाओं पर सटीक रूप से सटीक है (जो कि क्यू सख्त जरूरत है)।
टोनी डेलरॉय

you don't know what its current value is1 बिंदु के बारे में, जब एक सहज विफलता होती है, तो क्या वर्तमान मूल्य उस त्वरित मूल्य के बराबर नहीं होना चाहिए? अन्यथा, यह एक वास्तविक विफलता होगी।
एरिक जेड

IMO, प्लेटफ़ॉर्म पर LL / SC का उपयोग करके कमजोर और मज़बूत संस्करण दोनों को लागू किया जाता है जिसमें कोई भी CAS हार्डवेयर आदिम नहीं होता है। तो मेरे लिए while(!compare_exchange_weak(..))और के बीच कोई प्रदर्शन अंतर क्यों है while(!compare_exchange_strong(..))?
एरिक जेड

क्षमा करें दोस्तों, मैंने एक और आखिरी सवाल जोड़ा।
एरिक जेड

1
@Jonathan: बस एक nitpick, लेकिन आप कर वर्तमान मूल्य पता है कि यह नकली तौर पर विफल रहता है (बेशक, कि अभी भी समय आप चर पढ़ द्वारा वर्तमान मूल्य एक और मुद्दा पूरी तरह से है कि क्या है, लेकिन यह कमजोर / strong की परवाह किए बिना है)। मैंने इसका उपयोग किया है, उदाहरण के लिए, एक वैरिएबल सेट करने के प्रयास का मान इसकी कीमत शून्य है, और यदि विफल होता है (अचानक या नहीं) कोशिश करते रहें, लेकिन केवल वास्तविक मूल्य क्या है पर निर्भर करता है।
कैमरन

17

विभिन्न ऑनलाइन संसाधनों (जैसे, यह एक और यह एक ), सी ++ 11 मानक, साथ ही साथ यहां दिए गए उत्तर के माध्यम से जाने के बाद, मैं स्वयं इसका उत्तर देने का प्रयास कर रहा हूं ।

संबंधित प्रश्न मर्ज किए गए हैं (उदाहरण के लिए, " क्यों? अपेक्षित? " का विलय "क्यों एक लूप में तुलना_exchange_weak (? )" के साथ किया जाता है और उसके अनुसार उत्तर दिए जाते हैं।


लगभग सभी उपयोगों में तुलना_exchange_weak () क्यों होती है?

विशिष्ट पैटर्न ए

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

expected = current.load();
do desired = function(expected);
while (!current.compare_exchange_weak(expected, desired));

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

एक और उदाहरण म्यूटेक्स का उपयोग करके लागू करना है std::atomic<bool>। सबसे एक धागा पर पर जो धागा पहले सेट निर्भर करता है, एक समय में महत्वपूर्ण अनुभाग दर्ज कर सकते हैं currentकरने के लिए trueऔर पाश से बाहर निकलें।

विशिष्ट पैटर्न बी

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

expected = false;
// !expected: if expected is set to true by another thread, it's done!
// Otherwise, it fails spuriously and we should try again.
while (!current.compare_exchange_weak(expected, true) && !expected);

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

उस ने कहा, compare_exchange_weak()एक लूप के बाहर का उपयोग करना दुर्लभ होना चाहिए । इसके विपरीत, ऐसे मामले हैं जो मजबूत संस्करण उपयोग में हैं। उदाहरण के लिए,

bool criticalSection_tryEnter(lock)
{
  bool flag = false;
  return lock.compare_exchange_strong(flag, true);
}

compare_exchange_weak यहाँ उचित नहीं है क्योंकि जब यह असफलता के कारण वापस आता है, तो यह संभावना है कि कोई भी महत्वपूर्ण अनुभाग पर कब्जा नहीं करता है।

भूखा धागा?

एक बात और ध्यान देने योग्य है कि क्या होता है अगर फालतू असफलताएँ होती रहें तो इस प्रकार धागे को भूखा रखा जा सकता है? सैद्धांतिक रूप से यह प्लेटफार्मों पर तब हो सकता है जब compare_exchange_XXX()निर्देशों के अनुक्रम के रूप में लागू किया जाता है (जैसे, एलएल / एससी)। एलएल और एससी के बीच एक ही कैश लाइन की बार-बार पहुंच लगातार असफलताएं पैदा करेगी। एक अधिक यथार्थवादी उदाहरण एक गूंगा समयबद्धन के कारण है जहां सभी समवर्ती धागे निम्नलिखित तरीके से इंटरलेय किए गए हैं।

Time
 |  thread 1 (LL)
 |  thread 2 (LL)
 |  thread 1 (compare, SC), fails spuriously due to thread 2's LL
 |  thread 1 (LL)
 |  thread 2 (compare, SC), fails spuriously due to thread 1's LL
 |  thread 2 (LL)
 v  ..

ऐसा हो सकता है?

यह हमेशा के लिए नहीं होगा, सौभाग्य से, सी + + 11 के लिए धन्यवाद:

कार्यान्वयन को यह सुनिश्चित करना चाहिए कि कमजोर तुलना और विनिमय संचालन लगातार झूठे नहीं होते हैं जब तक कि परमाणु वस्तु की अपेक्षा से अलग मूल्य नहीं होता है या परमाणु वस्तु के समवर्ती संशोधन होते हैं।

हम तुलना_एक्सचेंज_वॉक () का उपयोग क्यों करते हैं और खुद को लूप लिखते हैं? हम सिर्फ Compar_exchange_strong () का उपयोग कर सकते हैं।

निर्भर करता है।

केस 1: जब दोनों को एक लूप के अंदर उपयोग करने की आवश्यकता होती है। C ++ 11 कहता है:

जब एक तुलना और विनिमय एक लूप में होता है, तो कमजोर संस्करण कुछ प्लेटफार्मों पर बेहतर प्रदर्शन देगा।

X86 पर (कम से कम वर्तमान में। हो सकता है कि यह एलएल / एससी के रूप में एक परिचित योजना का सहारा लेगा जब प्रदर्शन के लिए एक दिन जब अधिक कोर पेश किया जाता है), कमजोर और मजबूत संस्करण अनिवार्य रूप से समान हैं क्योंकि वे दोनों एकल निर्देश को उबालते हैं cmpxchg। कुछ अन्य प्लेटफ़ॉर्म पर जहां एटोमिकलीcompare_exchange_XXX() लागू नहीं किया गया है (यहाँ अर्थ है कि कोई एकल हार्डवेयर आदिम मौजूद नहीं है), लूप के अंदर का कमजोर संस्करण लड़ाई जीत सकता है क्योंकि मजबूत व्यक्ति को फेल्योर विफलताओं को संभालना होगा और तदनुसार रिट्रीट करना होगा।

परंतु,

शायद ही कभी, हम पसंद कर सकते हैं compare_exchange_strong()भर में compare_exchange_weak()भी एक पाश में। उदाहरण के लिए, जब परमाणु चर के बीच बहुत सी चीजें होती हैं और एक परिकलित नए मूल्य का आदान-प्रदान होता है ( function()ऊपर देखें )। यदि परमाणु चर स्वयं बार-बार परिवर्तित नहीं होता है, तो हमें हर असफल विफलता के लिए महंगी गणना को दोहराने की आवश्यकता नहीं है। इसके बजाय, हम उम्मीद कर सकते हैं कि compare_exchange_strong()ऐसी विफलताओं को "अवशोषित" करें और हम केवल गणना को दोहराते हैं जब यह वास्तविक मूल्य परिवर्तन के कारण विफल हो जाता है।

केस 2: जब केवल compare_exchange_weak() एक लूप के अंदर उपयोग करने की आवश्यकता होती है। C ++ 11 भी कहता है:

जब एक कमजोर तुलना और विनिमय के लिए एक लूप की आवश्यकता होगी और एक मजबूत नहीं होगा, तो मजबूत एक बेहतर होगा।

यह आमतौर पर तब होता है जब आप कमजोर संस्करण से केवल असफल विफलताओं को खत्म करने के लिए लूप करते हैं। आप तब तक पुनः प्रयास करते हैं जब तक कि समवर्ती लेखन के कारण विनिमय या तो सफल नहीं हो जाता या विफल हो जाता है।

expected = false;
// !expected: if it fails spuriously, we should try again.
while (!current.compare_exchange_weak(expected, true) && !expected);

सबसे अच्छे रूप में, यह पहियों को फिर से मजबूत कर रहा है और जैसे ही प्रदर्शन करता है compare_exchange_strong()। और भी बुरा? यह दृष्टिकोण उन मशीनों का पूरा लाभ उठाने में विफल रहता है जो हार्डवेयर में गैर-सहज तुलना और विनिमय प्रदान करती हैं

अंतिम, यदि आप अन्य चीजों के लिए लूप करते हैं (उदाहरण के लिए, ऊपर "विशिष्ट पैटर्न ए" देखें), तो एक अच्छा मौका है जिसे compare_exchange_strong()एक लूप में भी रखा जाएगा, जो हमें पिछले मामले में वापस लाता है।


13

ठीक है, इसलिए मुझे एक फ़ंक्शन की आवश्यकता है जो परमाणु बाएं-स्थानांतरण करता है। मेरे प्रोसेसर के पास इसके लिए एक देशी ऑपरेशन नहीं है, और मानक पुस्तकालय में इसके लिए कोई फ़ंक्शन नहीं है, इसलिए ऐसा लगता है कि मैं अपना खुद का लिख ​​रहा हूं। यहाँ जाता हैं:

void atomicLeftShift(std::atomic<int>* var, int shiftBy)
{
    do {
        int oldVal = std::atomic_load(var);
        int newVal = oldVal << shiftBy;
    } while(!std::compare_exchange_weak(oldVal, newVal));
}

अब, दो कारण हैं कि लूप को एक से अधिक बार निष्पादित किया जा सकता है।

  1. जब मैं अपनी बाईं पारी कर रहा था तब किसी और ने परिवर्तन किया। मेरी गणना के परिणामों को परमाणु चर पर लागू नहीं किया जाना चाहिए, क्योंकि यह प्रभावी रूप से किसी और के लिखने को मिटा देगा।
  2. मेरा CPU फट गया और कमजोर CAS सहज रूप से विफल हो गया।

मैं ईमानदारी से परवाह नहीं है जो एक। लेफ्ट शिफ्टिंग इतनी तेज है कि मैं बस फिर से कर सकता हूं, भले ही असफलता कितनी भी हो।

क्या है कम तेजी से है, हालांकि, अतिरिक्त कोड है कि मजबूत कैस जरूरतों क्रम मजबूत होना में कमजोर आसपास कैस रैप करने के लिए है। कमजोर कैस के सफल होने पर यह कोड ज्यादा कुछ नहीं करता है ... लेकिन जब यह विफल हो जाता है, तो मजबूत कैस को यह निर्धारित करने के लिए कुछ जासूसी का काम करने की आवश्यकता है कि क्या यह केस 1 या केस 2 था। यह जासूसी का काम दूसरे लूप का रूप लेता है। प्रभावी रूप से मेरे अपने पाश के अंदर। दो नेस्टेड छोरों। अभी आप अपने एल्गोरिदम शिक्षक की कल्पना कीजिए।

और जैसा कि मैंने पहले उल्लेख किया है, मुझे उस जासूसी काम के परिणाम की परवाह नहीं है! किसी भी तरह से मैं कैस को फिर से करने जा रहा हूं। तो मजबूत कैस का उपयोग करने से मुझे कुछ भी हासिल नहीं होता है, और दक्षता की एक छोटी लेकिन औसत दर्जे की मात्रा मुझे खो देती है।

दूसरे शब्दों में, कमजोर सीएएस का उपयोग परमाणु अद्यतन कार्यों को लागू करने के लिए किया जाता है। जब आप कैस के परिणाम की परवाह करते हैं तो मजबूत CAS का उपयोग किया जाता है।


0

मुझे लगता है कि ऊपर दिए गए अधिकांश उत्तर "स्प्यूरियस फेल" कुछ प्रकार की समस्या के रूप में हैं, प्रदर्शन वीएस शुद्धता ट्रेडऑफ़।

यह देखा जा सकता है कि ज्यादातर समय कमजोर संस्करण तेज होता है, लेकिन तेजी से असफल होने की स्थिति में यह धीमा हो जाता है। और मजबूत संस्करण एक ऐसा संस्करण है जिसमें सहज विफलता की कोई संभावना नहीं है, लेकिन यह लगभग हमेशा धीमा है।

मेरे लिए, मुख्य अंतर यह है कि ये दो संस्करण ABA समस्या को कैसे संभालते हैं:

कमजोर संस्करण केवल तभी सफल होगा जब किसी ने लोड और स्टोर के बीच कैश लाइन को नहीं छुआ है, इसलिए यह एबीए समस्या का 100% पता लगाएगा।

मजबूत संस्करण केवल तभी विफल होगा जब तुलना विफल हो जाती है, इसलिए यह अतिरिक्त उपायों के बिना एबीए समस्या का पता नहीं लगाएगा।

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

लेकिन, x86 (मजबूत-ऑर्डर आर्किटेक्चर) पर, कमजोर संस्करण और मजबूत संस्करण समान हैं, और वे दोनों एबीए समस्या से ग्रस्त हैं।

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

निष्कर्ष रूप में - पोर्टेबिलिटी और प्रदर्शन कारणों के लिए, मजबूत संस्करण हमेशा एक बेहतर-या-समान विकल्प होता है।

कमजोर संस्करण केवल एक बेहतर विकल्प हो सकता है अगर यह आपको एबीए काउंटरस्मैशर्स को पूरी तरह से छोड़ने देता है या आपका एल्गोरिथ्म एबीए के बारे में परवाह नहीं करता है।

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