विभिन्न ऑनलाइन संसाधनों (जैसे, यह एक और यह एक ), सी ++ 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;
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;
while (!current.compare_exchange_weak(expected, true) && !expected);
सबसे अच्छे रूप में, यह पहियों को फिर से मजबूत कर रहा है और जैसे ही प्रदर्शन करता है compare_exchange_strong()
। और भी बुरा? यह दृष्टिकोण उन मशीनों का पूरा लाभ उठाने में विफल रहता है जो हार्डवेयर में गैर-सहज तुलना और विनिमय प्रदान करती हैं ।
अंतिम, यदि आप अन्य चीजों के लिए लूप करते हैं (उदाहरण के लिए, ऊपर "विशिष्ट पैटर्न ए" देखें), तो एक अच्छा मौका है जिसे compare_exchange_strong()
एक लूप में भी रखा जाएगा, जो हमें पिछले मामले में वापस लाता है।