क्या 'int num' के लिए num ++ परमाणु हो सकता है?


153

सामान्य तौर पर, के लिए int num, num++(या ++num), पढ़ने-संशोधित-लिखने के संचालन के रूप में, परमाणु नहीं है । लेकिन मुझे अक्सर कंपाइलर दिखाई देते हैं, उदाहरण के लिए जीसीसी , इसके लिए निम्न कोड जनरेट करें ( यहां देखें ):

यहां छवि विवरण दर्ज करें

चूंकि पंक्ति 5, जो num++एक निर्देश से मेल खाती है , क्या हम यह निष्कर्ष निकाल सकते हैं कि इस मामले में num++ परमाणु क्या है?

और यदि ऐसा है, तो इसका मतलब यह है कि num++डेटा-रेस के किसी भी खतरे के बिना समवर्ती (बहु-थ्रेडेड) परिदृश्यों में तथाकथित उत्पन्न किया जा सकता है (अर्थात हमें इसे बनाने की आवश्यकता नहीं है, उदाहरण के लिए, std::atomic<int>और संबंधित लागतों को लागू करना, क्योंकि यह तब से है वैसे भी परमाणु)?

अपडेट करें

सूचना है कि इस सवाल यह है कि नहीं है कि क्या वेतन वृद्धि है परमाणु (यह नहीं है और कहा कि था और सवाल के उद्घाटन लाइन है)। यह है कि क्या यह विशेष परिदृश्‍यों में हो सकता है, अर्थात एक उप-अनुदेश प्रकृति में कुछ मामलों में lockउपसर्ग के ऊपरी भाग से बचने के लिए शोषण किया जा सकता है । और, जैसा कि स्वीकृत उत्तर में यूनिप्रोसेसर मशीनों के बारे में अनुभाग में उल्लेख किया गया है, साथ ही साथ यह उत्तर , इसकी टिप्पणियों में बातचीत और अन्य बताते हैं, यह (हालांकि सी या सी ++ के साथ नहीं) हो सकता है।


65
तुमसे किसने कहा कि addपरमाणु है?
स्लाव

6
यह देखते हुए कि परमाणु की विशेषताओं में से एक को अनुकूलन के दौरान विशिष्ट प्रकार के पुनरावृत्ति की रोकथाम है, नहीं, वास्तविक ऑपरेशन की
परमाणुता

19
मैं यह भी बताना चाहूंगा कि यदि यह आपके प्लेटफॉर्म पर परमाणु है तो इस बात की कोई गारंटी नहीं है कि यह किसी अन्य प्लेटफॉर्म पर होगा। प्लेटफ़ॉर्म स्वतंत्र हो और एक का उपयोग करके अपने इरादे व्यक्त करें std::atomic<int>
NathanOliver

8
उस addनिर्देश के निष्पादन के दौरान , एक अन्य कोर इस कोर के कैश से उस मेमोरी पते को चुरा सकता है और इसे संशोधित कर सकता है। X86 CPU पर, addअनुदेश को एक lockउपसर्ग की आवश्यकता होती है यदि ऑपरेशन की अवधि के लिए पते को कैश में लॉक करने की आवश्यकता होती है।
डेविड श्वार्ट्ज

21
किसी भी ऑपरेशन के लिए "परमाणु" होना संभव है । आपको बस इतना करना होगा कि आप भाग्यशाली हों और कभी भी किसी भी ऐसी घटना को अंजाम न दें जिससे पता चले कि यह परमाणु नहीं है। परमाणु केवल गारंटी के रूप में मूल्यवान है । यह देखते हुए कि आप असेंबली कोड देख रहे हैं, सवाल यह है कि क्या यह विशिष्ट आर्किटेक्चर आपको गारंटी प्रदान करने के लिए होता है और क्या कंपाइलर गारंटी प्रदान करता है कि यह असेंबली स्तर का कार्यान्वयन है जो वे चुनते हैं।
Cort Ammon

जवाबों:


197

यह वही है जो C ++ डेटा रेस के रूप में परिभाषित करता है जो कि अपरिभाषित व्यवहार का कारण बनता है, भले ही एक संकलक कोड का उत्पादन करने के लिए हुआ हो, जो आपने कुछ लक्ष्य मशीन पर आशा की थी। आपको std::atomicविश्वसनीय परिणामों के लिए उपयोग करने की आवश्यकता है , लेकिन आप इसका उपयोग तब कर सकते हैं memory_order_relaxedजब आपको पुन: व्यवस्थित करने की परवाह नहीं है। नीचे कुछ उदाहरण कोड और asm आउटपुट का उपयोग करके देखें fetch_add


लेकिन सबसे पहले, सवाल का विधानसभा भाषा हिस्सा:

चूंकि संख्या ++ एक निर्देश ( add dword [num], 1) है, तो क्या हम यह निष्कर्ष निकाल सकते हैं कि इस मामले में संख्या ++ परमाणु है?

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

अन्य सीपीयू से मेमोरी ऑपरेशन लोड और स्टोर के बीच वैश्विक रूप से दृश्यमान हो सकते हैं। यानी add dword [num], 1लूप में चलने वाले दो धागे एक-दूसरे के स्टोर पर कदम रखेंगे। ( अच्छा चित्र के लिए @ मार्गरेट का जवाब देखें)। प्रत्येक दो थ्रेड्स से 40k वेतन वृद्धि के बाद, काउंटर केवल वास्तविक मल्टी-कोर x86 हार्डवेयर पर ~ 60k (80k नहीं) तक चला गया हो सकता है।


"परमाणु", ग्रीक शब्द से जिसका अर्थ अविभाज्य है, का अर्थ है कि कोई भी पर्यवेक्षक ऑपरेशन को अलग-अलग चरणों के रूप में नहीं देख सकता है । सभी बिट्स के लिए एक साथ शारीरिक / विद्युत रूप से तुरंत प्राप्त करना एक भार या स्टोर के लिए इसे प्राप्त करने का सिर्फ एक तरीका है, लेकिन यह ALU ऑपरेशन के लिए भी संभव नहीं है। मैं x86 पर Atomicity के अपने उत्तर में शुद्ध भार और शुद्ध दुकानों के बारे में बहुत अधिक विस्तार से गया था , जबकि यह उत्तर पढ़ने-संशोधित-लिखने पर केंद्रित है।

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

तो lock add dword [num], 1 है परमाणु । एक सीपीयू कोर जो यह निर्देश देता है कि निर्देश कैश स्थिति को अपने निजी L1 कैश में तब संशोधित स्थिति में रखेगा जब लोड कैश से डेटा पढ़ता है जब तक कि स्टोर अपना परिणाम कैश में वापस नहीं करता है। यह MESI कैश सुसंगतता प्रोटोकॉल (या मल्टी-कोर AMD AMD द्वारा उपयोग किए गए इसके MOESI / MESIF संस्करण) के नियमों के अनुसार सिस्टम में किसी भी अन्य कैश को लोड से स्टोर करने के लिए कैश लाइन की एक प्रति होने से रोकता है। इंटेल सीपीयू, क्रमशः)। इस प्रकार, अन्य कोर द्वारा ऑपरेशन या तो पहले या बाद में होते हैं, न कि दौरान।

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

(यदि कोई lockनिर्देश दो मेमोरी लाइनों पर फैला हुआ है, जो मेमोरी पर काम करता है, तो यह सुनिश्चित करने के लिए बहुत अधिक कार्य लेता है कि ऑब्जेक्ट के दोनों हिस्सों में परिवर्तन परमाणु बने रहें क्योंकि वे सभी पर्यवेक्षकों को प्रचारित करते हैं, इसलिए कोई पर्यवेक्षक फाड़ नहीं सकता। सीपीयू हो सकता है। जब तक डेटा मेमोरी नहीं मारता तब तक पूरी मेमोरी बस को लॉक करना है। अपने एटॉमिक वेरिएबल्स को मिसअलाइन न करें!

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


एक यूनिप्रोसेसर मशीन पर, या एकल-थ्रेडेड प्रक्रिया में , एक एकल RMW निर्देश वास्तव में एक lockउपसर्ग के बिना परमाणु है । अन्य कोड के लिए साझा चर तक पहुंचने का एकमात्र तरीका सीपीयू के लिए एक संदर्भ स्विच करना है, जो एक निर्देश के बीच में नहीं हो सकता है। तो एक प्लेन dec dword [num]सिंगल-थ्रेडेड प्रोग्राम और उसके सिग्नल हैंडलर के बीच या सिंगल-कोर मशीन पर चलने वाले मल्टी थ्रेडेड प्रोग्राम के बीच सिंक्रोनाइज़ कर सकता है। एक अन्य प्रश्न पर मेरे उत्तर के दूसरे भाग को देखें , और इसके तहत टिप्पणियां, जहां मैं इसे और अधिक विस्तार से समझाता हूं।


C ++ पर वापस:

num++कंपाइलर को बताए बिना उपयोग करने के लिए यह पूरी तरह से फर्जी है कि आपको इसे एक ही रीड-मॉडिफाई-राइट इंप्लीमेंट के संकलन के लिए चाहिए:

;; Valid compiler output for num++
mov   eax, [num]
inc   eax
mov   [num], eax

यदि आप numबाद के मूल्य का उपयोग करते हैं तो यह बहुत संभावना है : संकलक वृद्धि के बाद इसे एक रजिस्टर में लाइव रखेगा। इसलिए भले ही आप यह जांच लें कि num++अपने आप कैसे संकलित किया जाता है, आसपास के कोड को बदलने से यह प्रभावित हो सकता है।

(यदि बाद में मूल्य की आवश्यकता नहीं है, inc dword [num]तो प्राथमिकता दी जाती है; आधुनिक x86 सीपीयू तीन अलग-अलग निर्देशों का उपयोग करते हुए कम से कम कुशलता से एक मेमोरी-गंतव्य आरएमडब्ल्यू निर्देश चलाएगा। मजेदार तथ्य:gcc -O3 -m32 -mtune=i586 वास्तव में यह उत्सर्जन करेगा , क्योंकि (पेंटियम) पी 5 के सुपरस्कूलर पाइपलाइन से जमा हुआ है। जिस तरह से पी 6 और बाद में माइक्रोआर्किटेक्चर्स कई सरल माइक्रो-ऑपरेशंस के लिए जटिल निर्देश को डिकोड करते हैं। अधिक जानकारी के लिए एग्नर फॉग के इंस्ट्रक्शन टेबल / माइक्रोआर्किटेक्चर गाइड देखें। कई उपयोगी लिंक के लिए टैग विकी (इंटेल के x86 ISA मैनुअल सहित, जो स्वतंत्र रूप से पीडीएफ के रूप में उपलब्ध हैं)।


C ++ मेमोरी मॉडल के साथ लक्ष्य मेमोरी मॉडल (x86) को भ्रमित न करें

संकलन-समय पुन: व्यवस्थित करने की अनुमति है । आपको std के साथ जो मिलता है उसका दूसरा भाग :: परमाणु संकलन समय-सीमा के नियंत्रण पर है, यह सुनिश्चित करने के लिए कि आपकाnum++ कुछ अन्य ऑपरेशन के बाद ही आप विश्व स्तर पर दिखाई देते हैं।

क्लासिक उदाहरण: किसी डेटा को किसी अन्य थ्रेड को देखने के लिए बफ़र में संग्रहीत करना, फिर एक ध्वज सेट करना। भले ही x86 लोड / रिलीज स्टोर्स को मुफ्त में अधिग्रहित करता है, फिर भी आपको कंपाइलर को उपयोग करके पुन: व्यवस्थित नहीं करना है flag.store(1, std::memory_order_release);

आप उम्मीद कर रहे होंगे कि यह कोड अन्य थ्रेड्स के साथ सिंक्रनाइज़ होगा:

// flag is just a plain int global, not std::atomic<int>.
flag--;       // This isn't a real lock, but pretend it's somehow meaningful.
modify_a_data_structure(&foo);    // doesn't look at flag, and the compilers knows this.  (Assume it can see the function def).  Otherwise the usual don't-break-single-threaded-code rules come into play!
flag++;

लेकिन यह नहीं होगा। संकलक flag++फ़ंक्शन कॉल में स्थानांतरित करने के लिए स्वतंत्र है (यदि यह फ़ंक्शन को बताता है या जानता है कि यह नहीं दिखता है flag)। तब यह पूरी तरह से संशोधन को दूर कर सकता है, क्योंकि flagयह भी नहीं है volatile। (और नहीं, सी ++ volatileएसटीडी के लिए एक उपयोगी विकल्प नहीं है :: परमाणु। एसटीडी :: परमाणु कंपाइलर का मानना ​​है कि स्मृति में मूल्यों को एसिंक्रोनस रूप से समान रूप से संशोधित किया जा सकता है volatile, लेकिन इसके अलावा भी बहुत कुछ है।volatile std::atomic<int> foo यह नहीं है। जैसा कि std::atomic<int> foo@Richard Hodges के साथ चर्चा की गई है।)

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


जैसा कि मैंने उल्लेख किया है, x86 lockउपसर्ग एक पूर्ण मेमोरी बाधा है, इसलिए num.fetch_add(1, std::memory_order_relaxed);x86 पर समान कोड का उपयोग करना num++(डिफ़ॉल्ट क्रमिक स्थिरता है), लेकिन यह अन्य आर्किटेक्चर (जैसे एआरएम) पर बहुत अधिक कुशल हो सकता है। यहां तक ​​कि x86 पर, आराम से अधिक संकलन-समय पुन: व्यवस्थित करने की अनुमति मिलती है।

यह वही है जो जीसीसी वास्तव में x86 पर करता है, कुछ कार्यों के लिए जो एक std::atomicवैश्विक चर पर काम करते हैं ।

Godbolt संकलक एक्सप्लोरर पर अच्छी तरह से स्वरूपित स्रोत + विधानसभा भाषा कोड देखें । आप एआरएम, एमआइपीएस और पावरपीसी सहित अन्य लक्ष्य आर्किटेक्चर का चयन कर सकते हैं, यह देखने के लिए कि उन लक्ष्यों के लिए एटोमिक्स से आपको किस प्रकार की विधानसभा भाषा कोड मिलती है।

#include <atomic>
std::atomic<int> num;
void inc_relaxed() {
  num.fetch_add(1, std::memory_order_relaxed);
}

int load_num() { return num; }            // Even seq_cst loads are free on x86
void store_num(int val){ num = val; }
void store_num_release(int val){
  num.store(val, std::memory_order_release);
}
// Can the compiler collapse multiple atomic operations into one? No, it can't.

# g++ 6.2 -O3, targeting x86-64 System V calling convention. (First argument in edi/rdi)
inc_relaxed():
    lock add        DWORD PTR num[rip], 1      #### Even relaxed RMWs need a lock. There's no way to request just a single-instruction RMW with no lock, for synchronizing between a program and signal handler for example. :/ There is atomic_signal_fence for ordering, but nothing for RMW.
    ret
inc_seq_cst():
    lock add        DWORD PTR num[rip], 1
    ret
load_num():
    mov     eax, DWORD PTR num[rip]
    ret
store_num(int):
    mov     DWORD PTR num[rip], edi
    mfence                          ##### seq_cst stores need an mfence
    ret
store_num_release(int):
    mov     DWORD PTR num[rip], edi
    ret                             ##### Release and weaker doesn't.
store_num_relaxed(int):
    mov     DWORD PTR num[rip], edi
    ret

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


पुन :: @ विलय के बारे में टिप्पणी में चर्चा Hodges जवाब के बारे में संकलक एसटीजी :: परमाणु num++; num-=2;संचालन एक num--;निर्देश में : :

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

वर्तमान संकलक वास्तव में ऐसा नहीं करते हैं (अभी तक), लेकिन इसलिए नहीं कि उन्हें अनुमति नहीं है। C ++ WG21 / P0062R1: कंपाइलरों को एटॉमिक्स का अनुकूलन कब करना चाहिए? इस अपेक्षा पर चर्चा करता है कि कई प्रोग्रामर के पास यह है कि कंपाइलर "आश्चर्यजनक" अनुकूलन नहीं करेंगे, और मानक प्रोग्रामर को नियंत्रण देने के लिए क्या कर सकते हैं। N4455 उन चीजों के कई उदाहरणों पर चर्चा करता है जिन्हें इस एक सहित अनुकूलित किया जा सकता है। यह बताता है कि इनलाइनिंग और निरंतर-प्रसार ऐसी चीजों को पेश कर सकते हैं, fetch_or(0)जो मूल में बदलने में सक्षम हो सकती हैं load()(लेकिन अभी भी अधिग्रहित और जारी करना है), तब भी जब मूल स्रोत में कोई स्पष्ट रूप से अनावश्यक परमाणु ऑप्स नहीं थे।

वास्तविक कारण संकलक ऐसा नहीं करते (अभी तक) हैं: (1) किसी ने जटिल कोड नहीं लिखा है जो संकलक को सुरक्षित रूप से (कभी भी गलत हो रहा है) ऐसा करने की अनुमति देगा, और (2) यह संभवतः कम से कम के सिद्धांत का उल्लंघन करता है आश्चर्य है । पहली जगह में सही ढंग से लिखने के लिए लॉक-फ्री कोड पर्याप्त कठिन है। तो परमाणु हथियारों के आपके उपयोग में आकस्मिक मत बनो: वे सस्ते नहीं हैं और बहुत अनुकूलन नहीं करते हैं। यह हमेशा आसान नहीं होता है std::shared_ptr<T>, क्योंकि इसके साथ कोई गैर-परमाणु संस्करण नहीं होता है, हालाँकि, इसका कोई गैर-परमाणु संस्करण नहीं है (हालाँकि यहाँ एक उत्तरshared_ptr_unsynchronized<T> gcc को परिभाषित करने का आसान तरीका है )।


num++; num-=2;संकलन करने के लिए वापस आ रहे हैं जैसे कि यह था num--: कंपाइलरों को ऐसा करने की अनुमति है, जब तक कि numयह न हो volatile std::atomic<int>। यदि एक पुनरावृत्ति संभव है, तो जैसा कि नियम कंपाइलर को संकलन समय पर निर्णय लेने की अनुमति देता है कि यह हमेशा उस तरह से होता है। कुछ भी गारंटी नहीं है कि एक पर्यवेक्षक मध्यवर्ती मूल्यों ( num++परिणाम) को देख सकता है ।

Ie अगर ऑर्डरिंग जहां इन ऑपरेशंस के बीच विश्व स्तर पर कुछ भी दिखाई नहीं देता है, तो स्रोत की ऑर्डरिंग आवश्यकताओं (एब्सट्रैक्ट मशीन के लिए C ++ नियमों के अनुसार, लक्ष्य आर्किटेक्चर के अनुसार नहीं) के अनुरूप है, कंपाइलर / के lock dec dword [num]बजाय एक भी उत्सर्जन कर सकता है ।lock inc dword [num]lock sub dword [num], 2

num++; num--गायब नहीं हो सकता है, क्योंकि यह अभी भी अन्य धागे के साथ संबंध के साथ एक सिंक्रनाइज़ेशन है जो दिखता है num, और यह दोनों अधिग्रहण-लोड और एक रिलीज-स्टोर है जो इस धागे में अन्य संचालन के पुन: संचालन को अस्वीकार करता है। X86 के लिए, यह एक lock add dword [num], 0(यानी num += 0) के बजाय एक MFENCE को संकलित करने में सक्षम हो सकता है ।

जैसा कि PR0062 में चर्चा की गई है , संकलन समय पर गैर-आसन्न परमाणु ऑप्स का अधिक आक्रामक विलय बुरा हो सकता है (जैसे एक प्रगति काउंटर केवल हर पुनरावृत्ति के बजाय एक बार अपडेट हो जाता है), लेकिन यह डाउनसाइड्स के बिना प्रदर्शन में भी मदद कर सकता है (जैसे स्किपिंग) जब परमाणु की एक प्रति shared_ptrबनाई जाती है और नष्ट हो जाती है, तो रेफरी का परमाणु inc / dec मायने रखता है, यदि संकलक यह साबित कर सकता है कि shared_ptrअस्थायी के पूरे जीवनकाल के लिए एक और वस्तु मौजूद है।)

यहां तक ​​कि num++; num--विलय एक लॉक कार्यान्वयन की निष्पक्षता को चोट पहुंचा सकता है जब एक धागा अनलॉक होता है और तुरंत लॉक हो जाता है। अगर यह वास्तव में कभी भी asm में रिलीज़ नहीं होता है, तो भी हार्डवेयर आर्बिट्रेशन मैकेनिज़्म उस बिंदु पर लॉक को हथियाने का एक और मौका नहीं देगा।


वर्तमान gcc6.2 और clang3.9 के साथ, आपको अभी lockभी memory_order_relaxedसबसे स्पष्ट रूप से अनुकूलन योग्य मामले में भी अलग-अलग एड ऑपरेशन मिलते हैं । ( गॉडबोल्ट कंपाइलर एक्सप्लोरर ताकि आप देख सकें कि नवीनतम संस्करण अलग हैं।)

void multiple_ops_relaxed(std::atomic<unsigned int>& num) {
  num.fetch_add( 1, std::memory_order_relaxed);
  num.fetch_add(-1, std::memory_order_relaxed);
  num.fetch_add( 6, std::memory_order_relaxed);
  num.fetch_add(-5, std::memory_order_relaxed);
  //num.fetch_add(-1, std::memory_order_relaxed);
}

multiple_ops_relaxed(std::atomic<unsigned int>&):
    lock add        DWORD PTR [rdi], 1
    lock sub        DWORD PTR [rdi], 1
    lock add        DWORD PTR [rdi], 6
    lock sub        DWORD PTR [rdi], 5
    ret

1
"(अलग-अलग निर्देशों का उपयोग करते हुए] अधिक कुशल हुआ करता था ... लेकिन आधुनिक x86 सीपीयू एक बार फिर आरएमडब्ल्यू संचालन को कम से कम कुशलता से संभालते हैं" - यह अभी भी उस मामले में अधिक कुशल है जहां अद्यतन फ़ंक्शन का उपयोग बाद में उसी फ़ंक्शन में किया जाएगा और कंपाइलर के लिए इसे स्टोर करने के लिए एक निशुल्क रजिस्टर उपलब्ध है (और वेरिएबल को अस्थिर नहीं चिह्नित किया गया है)। इसका मतलब यह है कि यह अत्यधिक संभावना है कि संकलक एक निर्देश उत्पन्न करता है या ऑपरेशन के लिए एकाधिक फ़ंक्शन के बाकी कोड पर निर्भर करता है, न कि केवल प्रश्न में एकल पंक्ति।
पेरियाटा ब्रीटाटा

@PeriataBreatta: हाँ, अच्छी बात है। असम में आप mov eax, 1 xadd [num], eaxपोस्ट-इन्क्रीमेंट को लागू करने के लिए (बिना लॉक प्रीफ़िक्स के) का उपयोग कर सकते हैं num++, लेकिन यह वह नहीं है जो कंपाइलर करते हैं।
पीटर कॉर्ड्स

3
@ DavidC.Rankin: यदि आपके पास कोई ऐसा संपादन है जिसे आप बनाना चाहते हैं, तो नि: शुल्क महसूस करें। मैं इस CW हालांकि नहीं बनाना चाहता। यह अभी भी मेरा काम है (और मेरी गड़बड़: पी)। मैं अपने अंतिम [फ्रिस्बी] खेल के बाद कुछ साफ कर दूंगा :)
पीटर कॉर्ड्स

1
यदि समुदाय विकी नहीं है, तो शायद उपयुक्त टैग विकी पर एक लिंक। (दोनों x86 और परमाणु टैग?)। यह एसओ पर एक सामान्य खोज द्वारा एक उम्मीद के मुताबिक वापसी के बजाय अतिरिक्त लिंकेज है (अगर मुझे बेहतर पता था कि यह उस संबंध में कहां फिट होना चाहिए, तो मैं यह करूँगा। मुझे आगे और खुदाई करना होगा और टैग के नहीं करना चाहिए। विकी लिंकेज)
डेविड सी। रैंकिन

1
हमेशा की तरह - महान जवाब! सुसंगतता और परमाणुता के बीच अच्छा अंतर (जहां कुछ अन्य लोगों को यह गलत लगा)
लीवर

39

... और अब हम अनुकूलन सक्षम करते हैं:

f():
        rep ret

ठीक है, चलो इसे एक मौका दें:

void f(int& num)
{
  num = 0;
  num++;
  --num;
  num += 6;
  num -=5;
  --num;
}

परिणाम:

f(int&):
        mov     DWORD PTR [rdi], 0
        ret

एक अन्य अवलोकन सूत्र (यहां तक ​​कि कैश सिंक्रोनाइज़ेशन देरी की अनदेखी) में व्यक्तिगत परिवर्तनों का निरीक्षण करने का कोई अवसर नहीं है।

से तुलना:

#include <atomic>

void f(std::atomic<int>& num)
{
  num = 0;
  num++;
  --num;
  num += 6;
  num -=5;
  --num;
}

जहां परिणाम है:

f(std::atomic<int>&):
        mov     DWORD PTR [rdi], 0
        mfence
        lock add        DWORD PTR [rdi], 1
        lock sub        DWORD PTR [rdi], 1
        lock add        DWORD PTR [rdi], 6
        lock sub        DWORD PTR [rdi], 5
        lock sub        DWORD PTR [rdi], 1
        ret

अब, प्रत्येक संशोधन है: -

  1. एक और धागे में अवलोकन योग्य है, और
  2. अन्य धागों में हो रहे समान संशोधनों का सम्मान।

परमाणुता सिर्फ निर्देश स्तर पर नहीं है, इसमें प्रोसेसर से लेकर कैश के माध्यम से, मेमोरी और बैक तक पूरी पाइपलाइन शामिल है।

आगे की जानकारी

std::atomicS के अपडेट के अनुकूलन के प्रभाव के बारे में ।

C ++ मानक में 'if if ’नियम है, जिसके द्वारा यह कंपाइलर के लिए कोड को पुन: क्रमित करने के लिए अनुमत है, और यहां तक ​​कि कोड को फिर से लिखना भी प्रदान करता है, जिसके परिणाम में सटीक अवलोकन प्रभाव (साइड-इफेक्ट सहित) होते हैं जैसे कि उसने आपका निष्पादन किया हो कोड।

जैसे-यदि नियम रूढ़िवादी है, विशेष रूप से परमाणु शामिल हैं।

विचार करें:

void incdec(int& num) {
    ++num;
    --num;
}

चूँकि इंटर-थ्रेड अनुक्रमण को प्रभावित करने वाले कोई म्यूटेक्स लॉक्स, एटमिक्स या कोई अन्य निर्माण नहीं हैं, मैं तर्क दूंगा कि कंपाइलर इस फ़ंक्शन को एनओपी के रूप में फिर से लिखने के लिए स्वतंत्र है, जैसे:

void incdec(int&) {
    // nada
}

ऐसा इसलिए है क्योंकि c ++ मेमोरी मॉडल में वेतन वृद्धि के परिणाम को देखते हुए एक और सूत्र की संभावना नहीं है। यह निश्चित रूप से अलग करता है, तो हो सकता है numथा volatile(हो सकता है प्रभाव हार्डवेयर व्यवहार)। लेकिन इस मामले में, यह फ़ंक्शन इस मेमोरी को संशोधित करने वाला एकमात्र फ़ंक्शन होगा (अन्यथा प्रोग्राम बीमार है)।

हालाँकि, यह एक अलग बॉल गेम है:

void incdec(std::atomic<int>& num) {
    ++num;
    --num;
}

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

यहाँ एक डेमो है:

#include <thread>
#include <atomic>

int main()
{
    for (int iter = 0 ; iter < 20 ; ++iter)
    {
        std::atomic<int> num = { 0 };
        std::thread t1([&] {
            for (int i = 0 ; i < 10000000 ; ++i)
            {
                ++num;
                --num;
            }
        });
        std::thread t2([&] {
            for (int i = 0 ; i < 10000000 ; ++i)
            {
                num = 100;
            }
        });
        
        t2.join();
        t1.join();
        std::cout << num << std::endl;
    }
}

नमूना उत्पादन:

99
99
99
99
99
100
99
99
100
100
100
100
99
99
100
99
99
100
100
99

5
यह समझाने के लिए कि विफल रहता add dword [rdi], 1है नहीं परमाणु (बिना lockउपसर्ग)। लोड परमाणु है, और स्टोर परमाणु है, लेकिन लोड और स्टोर के बीच डेटा को संशोधित करने से कोई अन्य धागा नहीं रोकता है। तो स्टोर एक अन्य थ्रेड द्वारा किए गए संशोधन पर कदम रख सकता है। Jfdube.wordpress.com/2011/11/30/understanding-atomic-operations देखें । इसके अलावा, जेफ प्रेशिंग के लॉक-फ्री लेख बहुत अच्छे हैं , और वह उस इंट्रो लेख में मूल आरएमडब्ल्यू समस्या का उल्लेख करते हैं।
पीटर कॉर्ड्स

3
वास्तव में यहाँ क्या हो रहा है कि किसी ने भी इस अनुकूलन को gcc में लागू नहीं किया है, क्योंकि यह लगभग बेकार होगा और शायद सहायक से अधिक खतरनाक होगा। (सिद्धांत कम से कम आश्चर्य की। हो सकता है कि किसी को है एक अस्थायी स्थिति कभी कभी दिखाई दे सकता है उम्मीद, और सांख्यिकीय probabilty साथ ठीक हैं। या वे कर रहे हैं हार्डवेयर घड़ी अंक का उपयोग कर संशोधन पर बाधित करने के लिए।) सावधानी से गढ़ी जा करने के लिए ताला मुक्त कोड की जरूरत है, इसलिए अनुकूलन करने के लिए कुछ भी नहीं होगा। यह देखने के लिए और चेतावनी को मुद्रित करने के लिए उपयोगी हो सकता है, कोडर को सचेत करने के लिए कि उनके कोड का मतलब यह नहीं हो सकता है कि वे क्या सोचते हैं!
पीटर कॉर्ड्स

2
यह शायद कंपाइलरों के लिए इसे लागू नहीं करने का एक कारण है (कम से कम आश्चर्य और इतने पर सिद्धांत)। यह देखना कि वास्तविक हार्डवेयर पर अभ्यास करना संभव होगा। हालाँकि, C ++ मेमोरी ऑर्डर करने वाले नियम किसी भी गारंटी के बारे में कुछ नहीं कहते हैं कि एक धागे का लोड C ++ एब्सट्रैक्ट मशीन में अन्य थ्रेड ऑप्स के साथ "समान रूप से" मिश्रण करता है। मुझे अभी भी लगता है कि यह कानूनी होगा, लेकिन प्रोग्रामर-शत्रुतापूर्ण।
पीटर कॉर्ड्स 20

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

5
अंतिमता के लिए, मैंने std चर्चा मेलिंग सूची पर पूछा। इस प्रश्न ने 2 पेपरों को बदल दिया, जो पीटर के साथ दोनों के साथ मेल खाते हैं , और मुझे इस तरह के अनुकूलन के बारे में चिंता के बारे में पता है: wg21.link/p0062 और wg21.link/n4455 एंडी के लिए मेरा धन्यवाद जिन्होंने इनको मेरे ध्यान में लाया।
रिचर्ड हॉजेस

38

कई जटिलताओं के बिना जैसे एक निर्देश add DWORD PTR [rbp-4], 1बहुत सीआईएससी-शैली है।

यह तीन ऑपरेशन करता है: ऑपरेंड को मेमोरी से लोड करता है, इसे बढ़ाता है, ऑपरेंड को मेमोरी में वापस स्टोर करता है।
इन परिचालनों के दौरान सीपीयू दो बार बस को प्राप्त करता है और छोड़ता है, किसी अन्य एजेंट के बीच भी इसे प्राप्त कर सकता है और यह परमाणुता का उल्लंघन करता है।

AGENT 1          AGENT 2

load X              
inc C
                 load X
                 inc C
                 store X
store X

X केवल एक बार बढ़ा है।


7
@LeoHeinsaar इस मामले में ऐसा होने के लिए, प्रत्येक मेमोरी चिप को अपने स्वयं के अरिथमेटिक लॉजिक यूनिट (ALU) की आवश्यकता होगी। वास्तव में, यह आवश्यक होगा कि प्रत्येक मेमोरी चिप एक प्रोसेसर था
रिचर्ड हॉजेस

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

@PeterCordes आपकी टिप्पणी ठीक वही उत्तर है जिसकी मुझे तलाश थी। मार्गरेट के जवाब से मुझे संदेह हुआ कि ऐसा कुछ अंदर जाना चाहिए।
लियो हिंसार 16

प्रश्न के C ++ भाग को संबोधित करते हुए उस टिप्पणी को पूर्ण उत्तर में बदल दिया।
पीटर कॉर्ड्स

1
@PeterCordes धन्यवाद, बहुत विस्तृत और सभी बिंदुओं पर। यह स्पष्ट रूप से एक डेटा दौड़ था और इसलिए C ++ मानक द्वारा अपरिभाषित व्यवहार, मैं बस उत्सुक था कि क्या उन मामलों में जहां उत्पन्न कोड था जिसे मैंने पोस्ट किया था वह मान सकता है कि यह परमाणु आदि हो सकता है आदि मैंने अभी भी जाँच की थी कि कम से कम इंटेल डेवलपर मैनुअल बहुत स्पष्ट रूप से स्मृति संचालन के संबंध में परमाणुता को परिभाषित करते हैं और निर्देशहीनता को निर्देश नहीं देते हैं, जैसा कि मैंने माना: "अन्य सभी मेमोरी ऑपरेशन और बाहरी रूप से दिखाई देने वाली घटनाओं के संबंध में लॉक किए गए ऑपरेशन परमाणु हैं।"
सिंह हिंसार

11

जोड़ने का निर्देश परमाणु नहीं है । यह मेमोरी को संदर्भित करता है, और दो प्रोसेसर कोर में उस मेमोरी के विभिन्न स्थानीय कैश हो सकते हैं।

IIRC ऐड निर्देश के परमाणु संस्करण को लॉक xadd कहा जाता है


3
lock xaddऔजार C ++ std :: परमाणु fetch_add, पुराना मान लौटाता है। यदि आपको इसकी आवश्यकता नहीं है, तो संकलक lockप्रीफ़िक्स के साथ सामान्य मेमोरी डेस्टिनेशन निर्देशों का उपयोग करेगा । lock addया lock inc
पीटर कॉर्ड्स

1
add [mem], 1अभी भी बिना कैश वाले SMP मशीन पर परमाणु नहीं होगा, अन्य उत्तरों पर मेरी टिप्पणी देखें।
पीटर कॉर्ड्स

यह कैसे परमाणु नहीं है पर वास्तव में बहुत अधिक जानकारी के लिए मेरा जवाब देखें। इस संबंधित प्रश्न पर मेरे उत्तर का अंत भी ।
पीटर कॉर्ड्स

10

चूंकि पंक्ति 5, जो संख्या ++ से मेल खाती है, एक निर्देश है, क्या हम यह निष्कर्ष निकाल सकते हैं कि इस मामले में संख्या ++ परमाणु है?

"रिवर्स इंजीनियरिंग" उत्पन्न विधानसभा के आधार पर निष्कर्ष निकालना खतरनाक है। उदाहरण के लिए, आपको लगता है कि आपने अपने कोड को अनुकूलन अक्षम के साथ संकलित किया है, अन्यथा संकलक ने उस चर को फेंक दिया होगा या 1 को सीधे बिना इसे लोड किए operator++। क्योंकि ऑप्टिमाइज़ किए गए झंडे, लक्ष्य सीपीयू, आदि के आधार पर उत्पन्न असेंबली काफी बदल सकती है, आपका निष्कर्ष रेत पर आधारित है।

इसके अलावा, आपका विचार है कि एक विधानसभा निर्देश का मतलब है कि एक ऑपरेशन परमाणु भी गलत है। यह addबहु-सीपीयू प्रणालियों पर परमाणु नहीं होगा, यहां तक ​​कि x86 वास्तुकला पर भी।


9

यहां तक ​​कि अगर आपके कंपाइलर ने इसे हमेशा परमाणु ऑपरेशन के रूप में उत्सर्जित किया है, तो numकिसी भी अन्य थ्रेड से समवर्ती रूप से एक्सेस करना C ++ 11 और C ++ 14 मानकों के अनुसार एक डेटा रेस होगा और कार्यक्रम का अपरिभाषित व्यवहार होगा।

लेकिन यह उससे भी बदतर है। सबसे पहले, जैसा कि उल्लेख किया गया है, संकलक द्वारा उत्पन्न निर्देश जब एक चर बढ़ाते हैं, तो अनुकूलन स्तर पर निर्भर हो सकता है। दूसरे, संकलक अन्य मेमोरी एक्सेस को फिर से चालू कर सकता है ++numअगर numवह परमाणु नहीं है, जैसे

int main()
{
  std::unique_ptr<std::vector<int>> vec;
  int ready = 0;
  std::thread t{[&]
    {
       while (!ready);
       // use "vec" here
    });
  vec.reset(new std::vector<int>());
  ++ready;
  t.join();
}

यहां तक ​​कि अगर हम आशावादी हैं कि ++ready"परमाणु" है, और यह कि संकलक जाँच लूप को आवश्यकतानुसार उत्पन्न करता है (जैसा कि मैंने कहा, यह यूबी है और इसलिए संकलक इसे हटाने के लिए स्वतंत्र है, इसे अनंत लूप के साथ बदलें, आदि)। कंपाइलर अभी भी पॉइंटर असाइनमेंट को स्थानांतरित कर सकता है, या vectorवृद्धि के ऑपरेशन के बाद एक बिंदु पर आरंभीकरण से भी बदतर हो सकता है, जिससे नए धागे में अराजकता हो सकती है। व्यवहार में, मुझे बिल्कुल भी आश्चर्य नहीं होगा यदि एक अनुकूलन कंपाइलर ने readyचर और चेकिंग लूप को पूरी तरह से हटा दिया , क्योंकि यह भाषा के नियमों के तहत अवलोकन योग्य व्यवहार को प्रभावित नहीं करता है (जैसा कि आपकी निजी आशाओं के विपरीत है)।

वास्तव में, पिछले साल की बैठक सी ++ सम्मेलन में, मैंने दो संकलक डेवलपर्स से सुना है कि वे बहुत खुशी से अनुकूलन को लागू करते हैं जो भोलेपन से लिखे गए बहु-थ्रेडेड प्रोग्राम को दुर्व्यवहार करते हैं, जब तक कि भाषा के नियम इसे अनुमति देते हैं, यदि कोई मामूली प्रदर्शन सुधार भी दिखाई देता है। सही ढंग से लिखित कार्यक्रमों में।

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

एक लंबी कहानी को छोटा करने के लिए, थ्रेड-सुरक्षित प्रोग्रामिंग की प्राकृतिक जिम्मेदारियां हैं:

  1. आपका कर्तव्य कोड लिखना है जिसमें भाषा के नियमों (और विशेष रूप से भाषा मानक मेमोरी मॉडल) के तहत अच्छी तरह से परिभाषित व्यवहार है।
  2. आपका संकलक का कर्तव्य मशीन कोड उत्पन्न करना है जिसका लक्ष्य वास्तुकला की स्मृति मॉडल के तहत एक ही अच्छी तरह से परिभाषित (अवलोकन योग्य) व्यवहार है।
  3. आपका सीपीयू का कर्तव्य इस कोड को निष्पादित करना है ताकि मनाया गया व्यवहार अपनी वास्तुकला की मेमोरी मॉडल के साथ संगत हो।

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

पुनश्च: सही लिखित उदाहरण:

int main()
{
  std::unique_ptr<std::vector<int>> vec;
  std::atomic<int> ready{0}; // NOTE the use of the std::atomic template
  std::thread t{[&]
    {
       while (!ready);
       // use "vec" here
    });
  vec.reset(new std::vector<int>());
  ++ready;
  t.join();
}

यह सुरक्षित है क्योंकि:

  1. readyभाषा के नियमों के अनुसार जांच को दूर नहीं किया जा सकता है।
  2. ऐसा ++ready होता है- चेक से पहले जो readyशून्य के रूप में नहीं दिखता है, और इन ऑपरेशनों के आसपास अन्य संचालन को फिर से चालू नहीं किया जा सकता है। ऐसा इसलिए है क्योंकि ++readyऔर चेक क्रमिक रूप से सुसंगत हैं , जो कि C ++ मेमोरी मॉडल में वर्णित एक और शब्द है और जो इस विशिष्ट रूटिंग को मना करता है। इसलिए कंपाइलर को निर्देशों को पुन: व्यवस्थित नहीं करना चाहिए, और सीपीयू को यह भी बताना चाहिए कि यह vecवृद्धि के बाद लिखने को स्थगित नहीं करना चाहिए ready। भाषा के मानक में परमाणु के संबंध में क्रमिक रूप से सबसे मजबूत गारंटी है। कम (और सैद्धांतिक रूप से सस्ता) गारंटी अन्य तरीकों के माध्यम से उपलब्ध हैंstd::atomic<T>, लेकिन ये निश्चित रूप से केवल विशेषज्ञों के लिए हैं, और कंपाइलर डेवलपर्स द्वारा बहुत अधिक अनुकूलित नहीं किया जा सकता है, क्योंकि वे शायद ही कभी उपयोग किए जाते हैं।

1
यदि संकलक सभी उपयोगों को नहीं देख सकता है ready, तो यह संभवतः while (!ready);कुछ और जैसे संकलन करेगा if(!ready) { while(true); }। अपवर्तित: एसटीडी का एक महत्वपूर्ण हिस्सा :: परमाणु किसी भी बिंदु पर अतुल्यकालिक संशोधन मानने के लिए शब्दार्थ को बदल रहा है। यह होने के नाते यूबी सामान्य रूप से है जो कंपाइलरों को भार उठाने और लूप से बाहर स्टोर करने की अनुमति देता है।
पीटर कॉर्ड्स

9

सिंगल-कोर x86 मशीन पर, एक addनिर्देश आमतौर पर सीपीयू 1 पर अन्य कोड के संबंध में परमाणु होगा । एक व्यवधान एक निर्देश को बीच में विभाजित नहीं कर सकता है।

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

आधुनिक x86 सिस्टम मल्टी-कोर हैं, इसलिए यूनिप्रोसेसर विशेष मामला लागू नहीं होता है।

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

(यह, यदि आप C ++ रहे लेखन आपकी मदद नहीं करता है, हालांकि। संकलनकर्ता की आवश्यकता के लिए एक विकल्प नहीं है num++स्मृति गंतव्य जोड़ने के लिए संकलन या xadd के बिना एक lockउपसर्ग। वे लोड करने के लिए चुन सकते हैं numएक रजिस्टर और दुकान में वेतन वृद्धि एक अलग निर्देश के साथ होती है, और यदि आप परिणाम का उपयोग करते हैं तो यह संभव है।)


फुटनोट 1: lockउपसर्ग मूल 8086 पर भी मौजूद था क्योंकि I / O डिवाइस सीपीयू के साथ समवर्ती रूप से काम करते हैं; एकल-कोर सिस्टम पर ड्राइवरों को lock addडिवाइस मेमोरी में एक मूल्य बढ़ाने की आवश्यकता होती है यदि डिवाइस इसे संशोधित भी कर सकता है, या डीएमए पहुंच के संबंध में।


यह आम तौर पर परमाणु भी नहीं है: एक और धागा एक ही समय में एक ही चर को अपडेट कर सकता है और केवल एक अपडेट ही लिया जाता है।
फज

1
मल्टी-कोर सिस्टम पर विचार करें। बेशक, एक कोर के भीतर, निर्देश परमाणु है, लेकिन यह पूरे सिस्टम के संबंध में परमाणु नहीं है।
फज

1
@FUZxxl: मेरे उत्तर के चौथे और पांचवें शब्द क्या थे?
सुपरकट

1
@supercat आपका उत्तर बहुत ही भ्रामक है क्योंकि यह केवल एक ही कोर के दुर्लभ मामले पर विचार करता है और ओपी को सुरक्षा की झूठी भावना देता है। इसलिए मैंने मल्टी-कोर केस पर विचार करने के लिए टिप्पणी की।
फज

1
@FUxxxxl: मैंने उन पाठकों के लिए संभावित भ्रम को दूर करने के लिए एक संपादन किया, जिन्होंने ध्यान नहीं दिया कि यह सामान्य आधुनिक मल्टीकोर सीपीयू के बारे में बात नहीं कर रहा है। (और भी कुछ सामान के बारे में अधिक विशिष्ट होना चाहिए कि सुपरकैट निश्चित नहीं था)। बीटीडब्ल्यू, इस उत्तर में सब कुछ पहले से ही है, अंतिम वाक्य को छोड़कर कि कैसे प्लेटफ़ॉर्म जहां रीड-संशोधित-राइट परमाणु "मुफ्त में" दुर्लभ हैं।
पीटर कॉर्ड्स

7

दिन में जब x86 कंप्यूटर में एक सीपीयू होता था, एक निर्देश का उपयोग सुनिश्चित करता था कि व्यवधान पढ़ने / संशोधित / लिखने के लिए विभाजित नहीं होगा और यदि मेमोरी को डीएमए बफर के रूप में भी इस्तेमाल नहीं किया जाएगा, तो यह वास्तव में परमाणु था (और C ++ ने मानक में थ्रेड्स का उल्लेख नहीं किया है, इसलिए इसे संबोधित नहीं किया गया था)।

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

आज, यह केवल एक ही सीपीयू आत्मीयता के लिए सेट किए गए कई थ्रेड्स के खिलाफ मदद करेगा, इसलिए जिन थ्रेड्स के बारे में आप चिंतित हैं, वे केवल एक ही CPU (कोर) पर दूसरे थ्रेड को समाप्त करने और चलाने के माध्यम से खेलने में आएंगे। वह यथार्थवादी नहीं है।

आधुनिक x86 / x64 प्रोसेसर के साथ, एकल निर्देश कई माइक्रो ऑप्स में टूट जाता है और इसके अलावा मेमोरी रीडिंग और राइटिंग बफर हो जाती है। इसलिए अलग-अलग सीपीयू पर चलने वाले अलग-अलग धागे न केवल इसे गैर-परमाणु के रूप में देखेंगे, बल्कि स्मृति से पढ़ी गई सामग्री के बारे में असंगत परिणाम भी देख सकते हैं और यह मानता है कि अन्य सूत्र उस समय तक पढ़ चुके हैं: आपको समझदारी दिखाने के लिए स्मृति बाड़ जोड़ने की जरूरत है व्यवहार।


1
बीच में आता है अभी भी इसलिए वे, नहीं विभाजन आरएमडब्ल्यू कार्य कर करते हैं अभी भी संकेत संचालकों के साथ एक एकल धागा सिंक्रनाइज़ है कि एक ही धागे में रन। बेशक, यह केवल तभी काम करता है जब एसम एक एकल निर्देश का उपयोग करता है, न कि अलग लोड / संशोधित / स्टोर। C ++ 11 इस हार्डवेयर कार्यक्षमता को उजागर कर सकता है, लेकिन ऐसा नहीं है (शायद इसलिए कि यह केवल यूनीप्रोसेसर कर्नेल में उपयोगी था, जो कि बाधित हैंडलर के साथ सिंक्रनाइज़ करने के लिए था, सिग्नल हैंडलर के साथ उपयोगकर्ता-स्थान में नहीं)। इसके अलावा आर्किटेक्चर में मेमोरी-डेस्टिनेशन निर्देशों को पढ़ना-संशोधित करना नहीं है। फिर भी, यह सिर्फ गैर-एक्स 86 पर एक आराम से परमाणु आरएमडब्ल्यू की तरह संकलित कर सकता है
पीटर कॉर्ड्स

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

हाँ क्षमा करें! मैंने वास्तव में ध्यान से नहीं पढ़ा। मैंने ऊप्स को डिकोड करने के बारे में लाल हेरिंग के साथ पैराग्राफ की शुरुआत देखी, और यह देखने के लिए कि आपने वास्तव में क्या कहा है, यह पढ़ने के लिए समाप्त नहीं हुआ। पुन :: 486: मुझे लगता है कि मैंने पढ़ा है कि शुरुआती एसएमपी किसी तरह का कॉम्पैक 386 था, लेकिन इसके मेमोरी-ऑर्डरिंग शब्दार्थ के रूप में वही नहीं थे जो वर्तमान में x86 आईएसए कहते हैं। वर्तमान x86 मैनुअल में SMP 486 का भी उल्लेख हो सकता है। वे निश्चित रूप से HPC (बियोवुल्फ़ क्लस्टर्स) में समान नहीं थे, जब तक कि Ppro / Athlon XP दिनों तक, हालांकि, मुझे लगता है।
पीटर कॉर्ड्स

1
@PeterCordes ठीक है। निश्चित रूप से, यह मानते हुए कि डीएमए / डिवाइस पर्यवेक्षक भी नहीं हैं - टिप्पणी क्षेत्र में फिट नहीं हुआ है कि इसमें एक भी शामिल है। धन्यवाद JDługosz उत्कृष्ट इसके अलावा (जवाब के साथ ही टिप्पणी)। वास्तव में चर्चा पूरी की।
सिंह हिंसार

3
@Leo: एक प्रमुख बिंदु जिसका उल्लेख नहीं किया गया है: आउट-ऑफ-ऑर्डर सीपीयू आंतरिक रूप से चीजों को फिर से व्यवस्थित करते हैं, लेकिन सुनहरा नियम यह है कि एकल कोर के लिए , वे एक बार में चल रहे निर्देशों के भ्रम को संरक्षित करते हैं, क्रम में। (और इसमें प्रसंग स्विच स्विच को बाधित करता है)। मानों को क्रम से मेमोरी में विद्युत रूप से संग्रहीत किया जा सकता है, लेकिन एकल कोर जो सब कुछ चल रहा है, सभी भ्रमों को ट्रैक करता है जो भ्रम को संरक्षित करने के लिए स्वयं करता है। यही कारण है कि a = 1; b = a;आपको केवल आपके द्वारा संग्रहीत 1 को सही ढंग से लोड करने के लिए asm के बराबर एक मेमोरी बैरियर की आवश्यकता नहीं है ।
पीटर कॉर्डेस

4

नहीं। https://www.youtube.com/watch?v=31g0YE61PLQ (यह "द ऑफिस" के "नहीं" दृश्य की एक कड़ी है)

क्या आप सहमत हैं कि यह कार्यक्रम के लिए एक संभावित आउटपुट होगा:

नमूना उत्पादन:

100
100
100
100
100
100
100
100
100
100
100
100
100
100
100
100
100
100
100
100

यदि ऐसा है, तो संकलक यह सुनिश्चित करने के लिए स्वतंत्र है कि जो भी संकलक चाहता है, उस कार्यक्रम के लिए केवल संभव आउटपुट। यानी एक मुख्य () जो सिर्फ 100 में डालता है।

यह "जैसा-अगर" नियम है।

और आउटपुट की परवाह किए बिना, आप थ्रेड सिंक्रोनाइज़ेशन के बारे में उसी तरह सोच सकते हैं - यदि थ्रेड ए करता है num++; num--;और थ्रेड बी numबार-बार पढ़ता है , तो एक संभावित वैध इंटरलेविंग यह है कि थ्रेड बी कभी भी बीच में नहीं पढ़ता है num++और num--। चूंकि वह इंटरलेविंग वैध है, इसलिए कंपाइलर केवल संभव इंटरलेविंग बनाने के लिए स्वतंत्र है । और सिर्फ incr / decr को पूरी तरह से हटा दें।

यहाँ कुछ दिलचस्प निहितार्थ हैं:

while (working())
    progress++;  // atomic, global

(यानी कुछ अन्य थ्रेड अपडेट प्रगति पर आधारित UI की कल्पना करें progress)

संकलक इसे में बदल सकते हैं:

int local = 0;
while (working())
    local++;

progress += local;

शायद यही मान्य है। लेकिन शायद वह नहीं जो प्रोग्रामर उम्मीद कर रहा था :-(

समिति अभी भी इस सामान पर काम कर रही है। वर्तमान में यह "काम करता है" क्योंकि कंपाइलर एटॉमिक्स को ज्यादा अनुकूलित नहीं करते हैं। लेकिन वह बदल रहा है।

और अगर progressअस्थिर भी था, तो भी यह मान्य होगा:

int local = 0;
while (working())
    local++;

while (local--)
    progress++;

: - /


यह उत्तर केवल उस पक्ष-प्रश्न का उत्तर दे रहा है जो रिचर्ड और मैं विचार कर रहे थे। हम अंत में यह हल: बाहर है कि हाँ बदल जाता है, सी ++ मानक है गैर पर संचालन के विलय की अनुमति देते हैं volatile, जब यह किसी भी अन्य नियमों को तोड़ने नहीं करता है, परमाणु वस्तुओं। दो मानक-चर्चा दस्तावेज बिल्कुल इसी पर चर्चा करते हैं ( रिचर्ड की टिप्पणी में लिंक ), एक ही प्रगति-काउंटर उदाहरण का उपयोग करते हुए। जब तक C ++ इसे रोकने के तरीकों का मानकीकरण नहीं करता है, तब तक यह एक गुणवत्ता-कार्यान्वयन का मुद्दा है।
पीटर कॉर्ड्स

हाँ, मेरा "नहीं" वास्तव में तर्क की पूरी लाइन का जवाब है। यदि सवाल सिर्फ "संकलक / कुछ संकलक / कार्यान्वयन पर परमाणु हो सकता है", तो उत्तर निश्चित है। उदाहरण के लिए, एक संकलक lockहर ऑपरेशन में जोड़ने का फैसला कर सकता है । या कुछ कंपाइलर + यूनिप्रोसेसर संयोजन जहां न तो पुन: व्यवस्थित किया गया (यानी "अच्छे राजभाषा दिवस") सब कुछ परमाणु है। लेकिन उस का क्या मतलब है? आप वास्तव में इस पर भरोसा नहीं कर सकते। जब तक आप नहीं जानते कि आप किस प्रणाली के लिए लिख रहे हैं। (फिर भी, बेहतर होगा कि परमाणु <int> उस सिस्टम पर कोई अतिरिक्त ऑप्स नहीं जोड़ता है। इसलिए आपको अभी भी मानक कोड लिखना चाहिए ...)
tony

1
ध्यान दें कि And just remove the incr/decr entirely.काफी सही नहीं है। यह अभी भी एक अधिग्रहण और रिलीज ऑपरेशन है num। X86 पर, num++;num--केवल MFENCE को संकलित किया जा सकता है, लेकिन निश्चित रूप से कुछ भी नहीं। (जब तक कि कंपाइलर का पूरा-प्रोग्राम विश्लेषण यह साबित कर सकता है कि कुछ भी नहीं है, तो अंकों के उस संशोधन के साथ sychronizes और इससे कोई फर्क नहीं पड़ता कि इससे पहले के कुछ स्टोर उसके बाद से लोड होने तक देरी हो रहे हैं।) जैसे कि यह एक अनलॉक और फिर से था। -लॉक-राइट-दूर-उपयोग के मामले में, आपके पास अभी भी दो अलग-अलग महत्वपूर्ण अनुभाग हैं (शायद mo_relaxed का उपयोग करके), एक बड़ा नहीं।
पीटर कॉर्ड्स

@PeterCordes आह हाँ, सहमत हुए।
टोनी

2

हाँ लेकिन...

परमाणु वह नहीं है जो आप कहने का मतलब है। आप शायद गलत बात पूछ रहे हैं।

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

क्या यह धागा-सुरक्षित है?

यह एक अलग सवाल है, और एक निश्चित "नहीं!" के साथ जवाब देने के कम से कम दो अच्छे कारण हैं

सबसे पहले, संभावना है कि एक अन्य कोर में L1 (L2 और ऊपर की तरफ) उस कैश लाइन की एक प्रति आमतौर पर साझा की जा सकती है, लेकिन L1 सामान्यतः प्रति कोर है!), और समवर्ती रूप से उस मूल्य को संशोधित करता है। बेशक, जो परमाणु रूप से भी होता है, लेकिन अब आपके पास दो "सही" (सही, परमाणु, संशोधित) मूल्य हैं - कौन सा वास्तव में सही है?
सीपीयू इसे किसी न किसी तरह से सुलझाएगा। लेकिन परिणाम वह नहीं हो सकता है जिसकी आप अपेक्षा करते हैं।

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

आपके पास एक गारंटी लागू करने की संभावना है कि स्मृति-वार होने वाली हर चीज का एहसास कुछ गारंटीकृत, अच्छी तरह से परिभाषित क्रम में होता है जहां आपके पास "गारंटी से पहले" हुआ है। यह आदेश "आराम" (के रूप में पढ़ें: कोई भी नहीं) या आपकी आवश्यकता के अनुसार सख्त हो सकता है।

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


2
लोड और स्टोर प्रत्येक परमाणु अलग-अलग होते हैं, लेकिन संपूर्ण के रूप में पूरा पढ़ा-संशोधित-लेखन ऑपरेशन निश्चित रूप से परमाणु नहीं है । कैश सुसंगत हैं, इसलिए कभी भी एक ही लाइन ( en.wikipedia.org/wiki/MESI_protocol ) की परस्पर विरोधी प्रतियां नहीं पकड़ सकते हैं । एक अन्य कोर में भी केवल पढ़ने योग्य प्रति नहीं हो सकती है जबकि इस कोर में यह संशोधित अवस्था में है। क्या गैर-परमाणु बनाता है कि आरएमडब्ल्यू कर कोर लोड और स्टोर के बीच कैश लाइन का स्वामित्व खो सकता है।
पीटर कॉर्ड्स

2
इसके अलावा, नहीं, पूरे कैश लाइनों को हमेशा एटोमिक रूप से स्थानांतरित नहीं किया जाता है। इस जवाब को देखें भले ही वे,, जहां यह प्रयोगात्मक प्रदर्शन किया जाता है कि एक बहु सॉकेट Opteron 16B SSE भंडार गैर परमाणु बनाता हाइपर साथ 8B मात्रा में कैश लाइनों स्थानांतरित करके कर रहे हैं एक ही प्रकार के एकल सॉकेट CPU के लिए परमाणु (क्योंकि लोड / स्टोर हार्डवेयर में L1 कैश के लिए 16B पथ है)। x86 केवल 8B तक के अलग लोड या स्टोर के लिए परमाणुता की गारंटी देता है।
पीटर कॉर्ड्स

संकलक को संरेखण छोड़ने का मतलब यह नहीं है कि स्मृति को 4-बाइट सीमा पर संरेखित किया जाएगा। संरेखण सीमाओं को बदलने के लिए संकलक के पास विकल्प या प्रैग्मस हो सकते हैं। यह उपयोगी है, उदाहरण के लिए, नेटवर्क धाराओं में कसकर पैक किए गए डेटा पर काम करने के लिए।
दिमित्री रुबानोविच

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

1
@Damon, प्रश्न में दिए गए उदाहरण में एक संरचना का उल्लेख नहीं है, लेकिन यह सवाल को केवल उन स्थितियों तक सीमित नहीं करता है जहां पूर्णांक संरचना के भाग नहीं हैं। PODs में निश्चित रूप से PoT का आकार हो सकता है और PoT संरेखित नहीं होना चाहिए। वाक्य रचना उदाहरण के लिए इस उत्तर पर एक नज़र डालें: stackoverflow.com/a/11772340/1219722 । तो यह शायद ही एक "परिष्कार" है क्योंकि इस तरह से घोषित किए गए पीओडी वास्तविक जीवन के कोड में नेटवर्किंग कोड में काफी उपयोग किए जाते हैं।
दिमित्री रुबानोविच

2

एक भी संकलक के उत्पादन, एक विशिष्ट CPU वास्तुकला पर, अनुकूलन अक्षम (के बाद से जीसीसी भी संकलन नहीं करता है के साथ कि ++करने के लिए addजब अनुकूलन के लिए एक त्वरित और गंदा उदाहरण में ), मतलब लगता है incrementing इस तरह से परमाणु है मतलब यह नहीं है इस मानक अनुरूप है ( जब numआप किसी थ्रेड में पहुंचने की कोशिश कर रहे हों तो अपरिभाषित व्यवहार करें ), और वैसे भी गलत है, क्योंकि addऐसा नहीं है 86 में परमाणु।

ध्यान दें कि परमाणु ( lockनिर्देश उपसर्ग का उपयोग करते हुए ) x86 पर अपेक्षाकृत भारी हैं ( यह प्रासंगिक उत्तर देखें) ), लेकिन अभी भी उल्लेखनीय रूप से एक म्यूटेक्स से कम है, जो इस उपयोग के मामले में बहुत उपयुक्त नहीं है।

निम्नलिखित परिणाम क्लैंग ++ 3.8 से संकलित किए जाते हैं -Os

संदर्भ द्वारा एक इंट्री बढ़ाना, "नियमित" तरीका:

void inc(int& x)
{
    ++x;
}

इसमें संकलित है:

inc(int&):
    incl    (%rdi)
    retq

संदर्भ द्वारा पारित एक अंतर को बढ़ाना, परमाणु तरीका:

#include <atomic>

void inc(std::atomic<int>& x)
{
    ++x;
}

यह उदाहरण, जो नियमित तरीके से बहुत अधिक जटिल नहीं है, बस lockउपसर्ग को inclनिर्देश में जोड़ा जाता है - लेकिन सावधानी, जैसा कि पहले कहा गया है कि यह सस्ता नहीं है। सिर्फ इसलिए कि असेंबली शॉर्ट दिखती है इसका मतलब यह नहीं है कि यह तेज है।

inc(std::atomic<int>&):
    lock            incl    (%rdi)
    retq

-2

जब आपका कंपाइलर वेतन वृद्धि के लिए केवल एक निर्देश का उपयोग करता है और आपकी मशीन एकल-थ्रेडेड है, तो आपका कोड सुरक्षित है। ^^


-3

एक गैर-x86 मशीन पर समान कोड संकलित करने का प्रयास करें, और आप जल्दी से बहुत अलग विधानसभा परिणाम देखेंगे।

इसका कारण परमाणु num++ प्रतीत होता है क्योंकि x86 मशीनों पर, 32-बिट पूर्णांक में वृद्धि, वास्तव में, परमाणु है (कोई स्मृति पुनर्प्राप्ति नहीं होती है)। लेकिन यह न तो सी ++ मानक की गारंटी है, और न ही ऐसी मशीन पर होने की संभावना है जो x86 निर्देश सेट का उपयोग नहीं करती है। इसलिए यह कोड रेस स्थितियों से सुरक्षित नहीं है।

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

कारण, तब, जब हमारे पास std::atomic<int>और इतने पर है कि जब आप एक वास्तुकला के साथ काम कर रहे हैं, जहां बुनियादी संगणना की परमाणुता की गारंटी नहीं है, तो आपके पास एक तंत्र है जो संकलक को परमाणु कोड उत्पन्न करने के लिए मजबूर करेगा।


"क्योंकि x86 मशीनों पर, 32-बिट पूर्णांक बढ़ाना, वास्तव में, परमाणु है।" क्या आप दस्तावेज़ीकरण का लिंक प्रदान कर सकते हैं जो इसका प्रमाण देता है?
स्लाव

8
यह x86 पर परमाणु नहीं है। यह एकल-कोर-सुरक्षित है, लेकिन अगर कई कोर हैं (और वहाँ हैं) तो यह बिल्कुल भी परमाणु नहीं है।
हैरोल्ड

क्या x86 addवास्तव में परमाणु की गारंटी है? मुझे आश्चर्य नहीं होगा अगर रजिस्टर वेतन वृद्धि परमाणु थे, लेकिन यह शायद ही उपयोगी है; रजिस्टर इन्क्रीमेंट को दूसरे थ्रेड के लिए दृश्यमान बनाने के लिए इसे मेमोरी में रखने की आवश्यकता होती है, जिससे परमाणुता को हटाते हुए इसे लोड और स्टोर करने के लिए अतिरिक्त निर्देशों की आवश्यकता होगी। मेरी समझ यह है कि यही कारण है कि lockनिर्देशों के लिए उपसर्ग मौजूद है; केवल उपयोगी परमाणु add, स्मृति पर लागू होता है, और lockयह सुनिश्चित करने के लिए उपसर्ग का उपयोग करता है कि कैश लाइन ऑपरेशन की अवधि के लिए बंद है
शैडो रेंजर

@Slava @Harold @ShadowRanger मैंने जवाब अपडेट किया। addपरमाणु है, लेकिन मैंने स्पष्ट किया कि इसका मतलब यह नहीं है कि कोड रेस-कंडीशन सुरक्षित है, क्योंकि परिवर्तन अभी विश्व स्तर पर दिखाई नहीं देते हैं।
Xirema

3
@Xirema है कि यह "परमाणु नहीं" बनाता परिभाषा हालांकि द्वारा
हेरोल्ड
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.