C ++ 11 में, आम तौर पर volatile
थ्रेडिंग के लिए उपयोग नहीं किया जाता है , केवल MMIO के लिए
लेकिन टीएल: डीआर, यह mo_relaxed
सुसंगत कैश (यानी सब कुछ) के साथ हार्डवेयर पर परमाणु की तरह "काम" करता है ; यह रजिस्टरों में var रखते हुए संकलक को रोकने के लिए पर्याप्त है। atomic
एटमॉसिटी या इंटर-थ्रू दृश्यता बनाने के लिए मेमोरी बैरियर की आवश्यकता नहीं होती है, केवल ऑपरेशन के पहले / उसके बाद प्रतीक्षा करने के लिए अलग-अलग वेरिएबल्स में इस थ्रेड के एक्सेस के बीच ऑर्डर बनाने के लिए। mo_relaxed
कभी किसी बाधा की जरूरत नहीं है, बस लोड, स्टोर, या आरएमडब्ल्यू।
C ++ 11 से पहले बुरे पुराने दिनों मेंvolatile
(और बाधाओं के लिए इनलाइन- asm) के साथ रोल-ही-एटोमिक्स , कुछ चीजों को काम करने का एकमात्र अच्छा तरीका था । लेकिन यह बहुत सारी मान्यताओं पर निर्भर करता है कि कार्यान्वयन कैसे काम करता है और किसी भी मानक द्वारा कभी इसकी गारंटी नहीं दी जाती है।std::atomic
volatile
उदाहरण के लिए, लिनक्स कर्नेल अभी भी अपने स्वयं के हाथ से लुढ़का एटोमिक्स का उपयोग करता है volatile
, लेकिन केवल कुछ विशिष्ट सी कार्यान्वयन (GNU C, क्लैंग और शायद ICC) का समर्थन करता है। आंशिक रूप से ऐसा इसलिए है क्योंकि GNU C एक्सटेंशन और इनलाइन as वाक्यविन्यास और शब्दार्थ, लेकिन यह भी क्योंकि यह कंपाइलर कैसे काम करता है, इसके बारे में कुछ मान्यताओं पर निर्भर करता है।
यह नई परियोजनाओं के लिए लगभग हमेशा गलत विकल्प है; आप उपयोग कर सकते हैं std::atomic
(के साथ std::memory_order_relaxed
) एक संकलक प्राप्त करने के लिए उसी कुशल मशीन कोड का उपयोग करें जिसे आप कर सकते हैं volatile
। थ्रेडिंग उद्देश्यों के लिए obsoletes के std::atomic
साथ । mo_relaxed
volatile
(शायद कुछ कंपाइलरों के साथ छूटे हुए अनुकूलन के आसपास कामatomic<double>
करने के अलावा ।)
std::atomic
मुख्यधारा के संकलक (जैसे जीसीसी और क्लैंग) पर आंतरिक कार्यान्वयन केवल आंतरिक रूप से उपयोग नहीं करता है volatile
; संकलक सीधे परमाणु भार, स्टोर और आरएमडब्ल्यू बिलिन कार्यों को उजागर करते हैं। (उदाहरण के लिए GNU C __atomic
बिल्डिंग्स जो "सादे" ऑब्जेक्ट पर काम करते हैं।)
अस्थिर व्यवहार में प्रयोग करने योग्य है (लेकिन ऐसा न करें)
कहा कि, वास्तविक सीपीयू पर मौजूदा C ++ क्रियान्वयन volatile
जैसी चीज़ों के लिए व्यवहार में प्रयोग करने योग्य है exit_now
, क्योंकि सीपीयू कैसे काम करते हैं (सुसंगत कैश) और साझा मान्यताओं के बारे में कैसे volatile
काम करना चाहिए। लेकिन बहुत अधिक नहीं, और अनुशंसित नहीं है । इस उत्तर का उद्देश्य यह बताना है कि मौजूदा सीपीयू और सी ++ कार्यान्वयन वास्तव में कैसे काम करते हैं। अगर आपको इस बात की कोई परवाह नहीं है, तो आपको बस इतना पता होना चाहिए कि थ्रेडिंग के लिए std::atomic
mo_relaxed obsoletes volatile
है।
(आईएसओ सी ++ मानक इस पर बहुत अस्पष्ट है, बस कह रही है कि volatile
एक्सेस को सी ++ एब्सट्रैक्ट मशीन के नियमों के अनुसार कड़ाई से मूल्यांकन किया जाना चाहिए, अनुकूलित नहीं है। यह देखते हुए कि वास्तविक कार्यान्वयन मशीन के मेमोरी एड्रेस-स्पेस का उपयोग C ++ एरो स्पेस के लिए करते हैं। इसका मतलब यह है कि volatile
रीड और असाइनमेंट को ऑब्जेक्ट-प्रतिनिधित्व को मेमोरी में एक्सेस करने के लिए निर्देशों को लोड / स्टोर करने के लिए संकलित करना है।)
जैसा कि एक और उत्तर बताता है, एक exit_now
ध्वज अंतर-धागा संचार का एक सरल मामला है जिसे किसी भी सिंक्रनाइज़ेशन की आवश्यकता नहीं है : यह प्रकाशित नहीं कर रहा है कि सरणी सामग्री तैयार है या ऐसा कुछ भी है। बस एक स्टोर जो दूसरे धागे में नहीं-अनुकूलित-दूर लोड द्वारा तुरंत देखा जाता है।
// global
bool exit_now = false;
// in one thread
while (!exit_now) { do_stuff; }
// in another thread, or signal handler in this thread
exit_now = true;
बिना अस्थिर या परमाणु, के रूप में करता है, तो नियम और कोई डेटा-दौड़ यूबी की धारणा एएसएम में यह एक बार अनुकूलन करने के लिए है कि केवल चेक के ध्वज एक संकलक की अनुमति देता है , में प्रवेश (या नहीं) अनंत लूप से पहले। वास्तविक संकलक के लिए वास्तविक जीवन में ऐसा ही होता है। (और आमतौर पर do_stuff
लूप से बाहर निकलने के कारण आमतौर पर बहुत अधिक अनुकूलन करते हैं, इसलिए किसी भी बाद के कोड ने परिणाम का उपयोग किया हो सकता है अगर हम लूप में प्रवेश नहीं करते हैं)।
// Optimizing compilers transform the loop into asm like this
if (!exit_now) { // check once before entering loop
while(1) do_stuff; // infinite loop
}
मल्टीथ्रेडिंग प्रोग्राम अनुकूलित मोड में अटका हुआ है, लेकिन सामान्य रूप से चलता है -O0 एक उदाहरण है (जीसीसी के एसएसएम आउटपुट के विवरण के साथ) यह वास्तव में x86-64 पर जीसीसी के साथ कैसे होता है। इसके अलावा MCU प्रोग्रामिंग - C ++ O2 ऑप्टिमाइज़ेशन टूट जाता है जबकि इलेक्ट्रॉनिक्स पर लूप । एक और उदाहरण दिखाता है।
हम आम तौर पर आक्रामक अनुकूलन चाहते हैं कि सीएसई और लहरा लूप से बाहर निकलता है, जिसमें वैश्विक चर शामिल हैं।
C ++ 11 से पहले,volatile bool exit_now
इस काम को सामान्य (सामान्य C ++ कार्यान्वयन पर) करने का एक तरीका था । लेकिन C ++ 11 में, डेटा-रेस UB अभी भी लागू होता है, volatile
इसलिए यह वास्तव में आईएसओ मानक द्वारा हर जगह काम करने की गारंटी नहीं है, यहां तक कि एचडब्ल्यू सुसंगत कैश भी।
ध्यान दें कि व्यापक प्रकारों के लिए, volatile
फाड़ने की कमी की कोई गारंटी नहीं देता है। मैंने उस अंतर को यहाँ अनदेखा कर दिया bool
क्योंकि यह सामान्य कार्यान्वयन पर एक गैर-मुद्दा है। लेकिन इसका एक हिस्सा यह भी है कि volatile
वह अभी भी आराम करने वाले परमाणु के बराबर डेटा-रेस यूबी के अधीन है।
ध्यान दें कि "जैसा कि इरादा था" का मतलब यह नहीं है कि exit_now
वास्तव में बाहर निकलने के लिए दूसरे धागे की प्रतीक्षा कर रहा है। या यह भी कि exit_now=true
इस धागे में बाद में परिचालन जारी रखने से पहले यह वाष्पशील स्टोर का इंतजार कर रहा है । ( atomic<bool>
डिफ़ॉल्ट के साथ mo_seq_cst
इसे कम से कम seq_cst लोड होने से पहले प्रतीक्षा करना होगा। कई ISAs पर आपको स्टोर के बाद पूर्ण अवरोध मिलेगा)।
C ++ 11 एक गैर-यूबी तरीका प्रदान करता है जो समान संकलन करता है
एक "चलते रहो" या "अब बाहर निकलें" झंडे के std::atomic<bool> flag
साथ उपयोग करना चाहिएmo_relaxed
का उपयोग करते हुए
flag.store(true, std::memory_order_relaxed)
while( !flag.load(std::memory_order_relaxed) ) { ... }
आपको ठीक वही asm (बिना किसी महंगी बाधा निर्देश के) देगा जो आपको मिलेगा volatile flag
।
साथ ही नो-फाड़, atomic
आपको एक थ्रेड में स्टोर करने और यूबी के बिना दूसरे में लोड करने की क्षमता भी देता है, इसलिए कंपाइलर लोड को लूप से बाहर नहीं फहरा सकता है। (कोई डेटा-रेस यूबी की धारणा यह है कि हम गैर-परमाणु गैर-वाष्पशील वस्तुओं के लिए जो आक्रामक अनुकूलन चाहते हैं, वह अनुमति देता है।) यह सुविधा atomic<T>
वैसी ही है जैसी volatile
शुद्ध भार और शुद्ध भंडार के लिए है।
atomic<T>
+=
परमाणु आरएमडब्ल्यू परिचालनों में भी बनाते और बनाते हैं (एक अस्थायी, संचालन में परमाणु भार की तुलना में बहुत अधिक महंगा, फिर एक अलग परमाणु स्टोर। यदि आप एक परमाणु आरएमडब्ल्यू नहीं चाहते हैं, तो एक स्थानीय अस्थायी के साथ अपना कोड लिखें)।
seq_cst
आपके द्वारा प्राप्त होने वाले डिफ़ॉल्ट ऑर्डर के साथ while(!flag)
, यह ऑर्डरिंग गारंटी को भी जोड़ता है। गैर-परमाणु एक्सेस और अन्य परमाणु एक्सेस तक।
(सिद्धांत रूप में, आईएसओ सी ++ मानक एटमिक्स के संकलन-समय के अनुकूलन से इंकार नहीं करता है। लेकिन व्यवहारिक संकलक में ऐसा नहीं है क्योंकि जब यह ठीक नहीं होगा, तो इसे नियंत्रित करने का कोई तरीका नहीं है। कुछ मामले ऐसे भी हैं, जहां volatile atomic<T>
हो सकता है। परमाणुओं के अनुकूलन पर पर्याप्त नियंत्रण रखें यदि कंपाइलरों ने अनुकूलन किया है, तो अब कम्पाइलर्स के लिए नहीं। देखें कि कंपाइलर्स निरर्थक एसटीडी मर्ज क्यों नहीं करते हैं :: परमाणु लिखते हैं? ध्यान दें कि wg21 / p0062 volatile atomic
वर्तमान कोड का उपयोग करने के खिलाफ ऑप्टिमाइज़ेशन को ऑप्टिमाइज़ करने के लिए उपयोग करते हैं । एटोमिक्स।)
volatile
वास्तव में वास्तविक सीपीयू पर इसके लिए काम करता है (लेकिन अभी भी इसका उपयोग नहीं करते हैं)
यहां तक कि कमजोर-ऑर्डर किए गए मेमोरी मॉडल (गैर-x86) के साथ भी । लेकिन असल में यह प्रयोग नहीं करते, उपयोग atomic<T>
के साथ mo_relaxed
बजाय !! इस अनुभाग का उद्देश्य वास्तविक सीपीयू कैसे काम करते हैं, इसके बारे में गलत धारणाओं को संबोधित करना है, औचित्य नहीं volatile
। यदि आप लॉकलेस कोड लिख रहे हैं, तो आप शायद प्रदर्शन के बारे में परवाह करते हैं। कैश और अंतर-थ्रेड संचार की लागत को समझना आमतौर पर अच्छे प्रदर्शन के लिए महत्वपूर्ण है।
वास्तविक सीपीयू में सुसंगत कैश / साझा मेमोरी होती है: एक कोर के स्टोर से विश्व स्तर पर दिखाई देने के बाद, कोई अन्य कोर एक स्टोक मूल्य को लोड नहीं कर सकता है । (यह भी देखें मिथक प्रोग्रामर सीपीयू कैश के बारे में विश्वास करते हैं जो जावा वाष्पशील के बारे में कुछ बात करता है, atomic<T>
seq_cst मेमोरी ऑर्डर के साथ C ++ के बराबर है ।)
जब मैं लोड कहता हूं , मेरा मतलब है कि एक एसम निर्देश है जो मेमोरी तक पहुंचता है। यह एक volatile
पहुंच सुनिश्चित करता है, और गैर-परमाणु / गैर-वाष्पशील C ++ चर के लैवल्यू-टू-रैवल्यू रूपांतरण के समान नहीं है। (जैसे local_tmp = flag
या while(!flag)
)।
केवल एक चीज जिसे आपको हराने की आवश्यकता है, वह संकलन-समय अनुकूलन है जो पहले चेक के बाद बिल्कुल भी लोड नहीं करता है। प्रत्येक पुनरावृत्ति पर कोई भी लोड + चेक पर्याप्त है, बिना किसी आदेश के। इस थ्रेड और मुख्य थ्रेड के बीच सिंक्रनाइज़ेशन के बिना, यह बात करने के लिए सार्थक नहीं है कि वास्तव में स्टोर कब हुआ, या लोड रिट का आदेश दिया गया। लूप में अन्य संचालन। केवल जब यह इस धागे को दिखाई दे रहा है तो यही मायने रखता है। जब आप एग्जिट_वन फ्लैग सेट देखते हैं, तो आप बाहर निकल जाते हैं। एक विशिष्ट x86 Xeon पर अंतर-कोर विलंबता अलग भौतिक कोर के बीच 40ns की तरह कुछ हो सकता है ।
सिद्धांत रूप में: सुसंगत कैश के बिना हार्डवेयर पर C ++ थ्रेड
मैं किसी भी तरह से इसे दूर से कुशल नहीं देख सकता, केवल शुद्ध आईएसओ सी ++ के साथ प्रोग्रामर को स्रोत कोड में स्पष्ट फ्लश करने की आवश्यकता के बिना।
सिद्धांत रूप में आपके पास एक मशीन पर C ++ कार्यान्वयन हो सकता है जो इस तरह नहीं था, जो अन्य कोर पर अन्य थ्रेड्स को दिखाई देने वाली चीजों को बनाने के लिए संकलक-उत्पन्न स्पष्ट फ्लश की आवश्यकता होती है । (या शायद एक बासी प्रति का उपयोग नहीं करने के लिए पढ़ता है)। C ++ मानक यह असंभव नहीं बनाता है, लेकिन C ++ की मेमोरी मॉडल को सुसंगत साझा-मेमोरी मशीनों पर कुशल होने के लिए डिज़ाइन किया गया है। उदाहरण के लिए, C ++ मानक यहां तक कि "रीड-रीड कोहेरेंस", "राइट-रीड कोहेरेंस" के बारे में बात करता है, आदि मानक में एक नोट भी हार्डवेयर से कनेक्शन को इंगित करता है:
http://eel.is/c++draft/intro.races#19
[नोट: चार पूर्ववर्ती सुसंगतता आवश्यकताओं को प्रभावी ढंग से एक ही वस्तु के लिए परमाणु संचालन के संकलक के पुन: संचालन को अस्वीकार कर देता है, भले ही दोनों ऑपरेशन आराम से लोड हो। यह प्रभावी रूप से C ++ परमाणु संचालन के लिए उपलब्ध अधिकांश हार्डवेयर द्वारा प्रदान की गई कैश सुसंगतता को प्रभावी बनाता है। - अंतिम नोट]
release
स्टोर के लिए कोई तंत्र नहीं है केवल खुद को और कुछ चुनिंदा पता-रेंजों को फ्लश करने के लिए: यह सब कुछ सिंक करना होगा क्योंकि यह नहीं पता होगा कि अन्य धागे क्या पढ़ना चाहते हैं यदि उनके अधिग्रहण-लोड ने इस रिलीज-स्टोर को देखा (गठन कर रहे हैं) रिलीज-सीक्वेंस जो कि थ्रेड्स में एक होने से पहले संबंध स्थापित करता है, गारंटी देता है कि राइटिंग थ्रेड द्वारा किए गए पहले गैर-परमाणु संचालन अब पढ़ने के लिए सुरक्षित हैं। जब तक कि यह रिलीज स्टोर के बाद उन्हें आगे नहीं लिखता ...) या कंपाइलर होगा यह साबित करने के लिए वास्तव में स्मार्ट होना चाहिए कि केवल कुछ कैश लाइनों को फ्लशिंग करने की आवश्यकता है।
संबंधित: क्या मेरा उत्तर NUMA पर mov + mfence सुरक्षित है? सुसंगत साझा मेमोरी के बिना x86 सिस्टम के गैर-अस्तित्व के बारे में विस्तार से जाता है। यह भी संबंधित: लोड और स्टोर एआरएम पर लोड / स्टोर के बारे में अधिक उसी स्थान पर पुन : व्यवस्थित करने के लिए ।
वहाँ रहे हैं मैं गैर सुसंगत साझा स्मृति के साथ समूहों लगता है, लेकिन वे एकल प्रणाली की छवि मशीनों नहीं कर रहे हैं। प्रत्येक सुसंगतता डोमेन एक अलग कर्नेल चलाता है, इसलिए आप इसके पार एकल C ++ प्रोग्राम के थ्रेड नहीं चला सकते हैं। इसके बजाय आप कार्यक्रम के अलग-अलग उदाहरण चलाते हैं (प्रत्येक का अपना पता स्थान: एक उदाहरण में संकेत दूसरे में मान्य नहीं हैं)।
उन्हें स्पष्ट फ्लश के माध्यम से एक-दूसरे के साथ संवाद करने के लिए, आप प्रोग्राम को निर्दिष्ट करने के लिए एमपीआई या अन्य संदेश-गुजरने वाले एपीआई का उपयोग करेंगे।
वास्तविक हार्डवेयर std::thread
कैश सुसंगतता सीमाओं में नहीं चलता है:
कुछ विषम एआरएम चिप्स साझा भौतिक पता स्थान लेकिन साथ मौजूद हैं, नहीं भीतरी साझा करने योग्य कैश डोमेन। तो सुसंगत नहीं। (उदाहरण टिप्पणी धागा A8 कोर और TI कोरारा AM335x की तरह एक Cortex-M3)।
लेकिन अलग-अलग गुठली उन कोर पर चलेगी, न कि एक एकल प्रणाली की छवि जो दोनों कोर में धागे चला सकती है। मैं किसी भी सी ++ कार्यान्वयन के बारे में नहीं जानता हूं जो std::thread
सीपीयू कोर के पार सुसंगत कैश के बिना थ्रेड चलाते हैं।
एआरएम के लिए विशेष रूप से, जीसीसी और क्लैंग जनरेट कोड को मानते हुए सभी धागे एक ही इनर-शेअरबल डोमेन में चलते हैं। वास्तव में, ARMv7 ISA मैनुअल कहता है
यह आर्किटेक्चर (ARMv7) एक उम्मीद के साथ लिखा गया है कि एक ही ऑपरेटिंग सिस्टम या हाइपरविजर का उपयोग करने वाले सभी प्रोसेसर एक ही इनर शेरेबल शैरबिलिटी डोमेन में हैं
इसलिए अलग-अलग डोमेन के बीच गैर-सुसंगत साझा मेमोरी अलग-अलग कर्नेल के तहत विभिन्न प्रक्रियाओं के बीच संचार के लिए साझा मेमोरी क्षेत्रों के स्पष्ट प्रणाली-विशिष्ट उपयोग के लिए केवल एक चीज है।
इस कंपाइलर में कोड-जीन dmb ish
(इनर शेअरबल बैरियर) बनाम dmb sy
(सिस्टम) मेमोरी बैरियर्स के बारे में इस CoreCLR चर्चा को भी देखें ।
मैं यह दावा करता हूं कि std::thread
गैर-सुसंगत कैश के साथ कोर के पार किसी भी अन्य आईएसए के लिए कोई सी ++ कार्यान्वयन नहीं है । मेरे पास यह सबूत नहीं है कि ऐसा कोई कार्यान्वयन मौजूद नहीं है, लेकिन यह अत्यधिक संभावना नहीं है। जब तक आप उस तरीके से काम करने वाले एचडब्ल्यू के एक विशिष्ट विदेशी टुकड़े को लक्षित नहीं कर रहे हैं, प्रदर्शन के बारे में आपकी सोच को सभी थ्रेड्स के बीच MESI की तरह कैश सुसंगतता माननी चाहिए। (अधिमानतः का उपयोग atomic<T>
मायनों में कि गारंटी देता है शुद्धता, हालांकि!)
सुसंगत कैश इसे सरल बनाता है
लेकिन सुसंगत कैश, एक रिलीज की दुकान को लागू करने के साथ एक मल्टी कोर सिस्टम पर सिर्फ आदेश इस सूत्र के भंडार के लिए कैश में प्रतिबद्ध हैं, किसी भी स्पष्ट फ्लशिंग नहीं कर रही है। ( https://preshing.com/20120913/acquire-and-release-semantics/ और https://preshing.com/20120710/memory-barriers-are-like-source-control-operations/ )। (और एक अधिग्रहण-लोड का मतलब दूसरे कोर में कैश तक पहुंच का आदेश देना है)।
एक मेमोरी बैरियर इंस्ट्रक्शन सिर्फ वर्तमान थ्रेड के लोड और / या स्टोर को ब्लॉक करता है जब तक कि स्टोर बफर नालियों; यह हमेशा अपने दम पर संभव के रूप में तेजी से होता है। ( क्या मेमोरी बैरियर यह सुनिश्चित करता है कि कैश सुसंगतता पूरी हो गई है? इस गलत धारणा को संबोधित करता है)। इसलिए यदि आपको ऑर्डर देने की आवश्यकता नहीं है, तो बस दूसरे थ्रेड्स में दृश्यता का संकेत दें, mo_relaxed
ठीक है। (और ऐसा है volatile
, लेकिन ऐसा मत करो।)
प्रोसेसर को C / C ++ 11 मैपिंग
भी देखें
मजेदार तथ्य: x86 पर, प्रत्येक एएसएम स्टोर एक रिलीज़-स्टोर है क्योंकि x86 मेमोरी मॉडल मूल रूप से seq-cst plus a store बफर (स्टोर फ़ॉरवर्डिंग के साथ) है।
अर्ध-संबंधित फिर से: स्टोर बफर, वैश्विक दृश्यता, और सुसंगतता: सी ++ 11 बहुत कम गारंटी देता है। अधिकांश वास्तविक आईएसएएस (पावरपीसी को छोड़कर) गारंटी देते हैं कि सभी धागे दो स्टोरों की उपस्थिति के आदेश पर दो अन्य धागे से सहमत हो सकते हैं। (औपचारिक कंप्यूटर-आर्किटेक्चर मेमोरी मॉडल शब्दावली में, वे "मल्टी-कॉपी परमाणु" हैं)।
एक और गलत धारणा है कि स्मृति बाड़ एएसएम निर्देश दुकान बफर फ्लश करने के लिए अन्य कोर हमारी दुकानों को देखने के लिए के लिए आवश्यक हैं है सब पर । वास्तव में स्टोर बफर हमेशा (एल 1 डी कैश के लिए) के रूप में उपवास के रूप में संभव के रूप में तेजी से करने की कोशिश कर रहा है, अन्यथा यह भर जाएगा और स्टाल निष्पादन। एक पूर्ण अवरोध / बाड़ क्या करता है वर्तमान थ्रेड को स्टोर बफर तक सूखा दिया जाता है , इसलिए हमारे बाद के भार हमारे पहले के स्टोरों के बाद वैश्विक क्रम में दिखाई देते हैं।
(x86 का जोरदार आदेश दिया गया asm मेमोरी मॉडल का अर्थ है कि volatile
x86 पर आप को पास देना बंद हो सकता है mo_acq_rel
, सिवाय इसके कि गैर-परमाणु चर के साथ संकलित समय पुनरावृत्ति अभी भी हो सकती है। लेकिन अधिकांश गैर- x86 में कमजोर रूप से स्मृति वाले मॉडल हैं volatile
और relaxed
लगभग उतने ही हैं। कमजोर की mo_relaxed
अनुमति देता है।)