मैंने सुना है कि मैं + धागा सुरक्षित नहीं है, क्या मैं + धागा-सुरक्षित है?


90

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

हालाँकि, मैं ++ i के बारे में सोच रहा हूँ। जहाँ तक मैं बता सकता हूँ, यह 'असेंबली आर 1, आर 1, 1' जैसे एक सिंगल असेंबली इंस्ट्रक्शन को कम करेगा और चूंकि यह केवल एक इंस्ट्रक्शन है, इसलिए यह एक संदर्भ स्विच द्वारा अबाधित होगा।

क्या कोई स्पष्ट कर सकता है? मैं मान रहा हूं कि x86 प्लेटफॉर्म का उपयोग किया जा रहा है।


सिर्फ एक प्रश्न। दो (या अधिक) थ्रेड्स के लिए किस तरह के परिदृश्यों की आवश्यकता होती है जैसे कि एक चर? मैं यहां ईमानदारी से पूछ रहा हूं, आलोचना नहीं कर रहा हूं। यह सिर्फ इस समय पर है, मेरा सिर किसी के बारे में नहीं सोच सकता है।
OscarRyz

5
C ++ क्लास में एक क्लास वेरिएबल एक ऑब्जेक्ट काउंट बनाए रखता है?
पैक्सिडाब्लो

1
इस मामले के बारे में अच्छा वीडियो मैंने आज ही देखा क्योंकि एक अन्य व्यक्ति ने मुझे बताया: youtube.com/watch?v=mrvAqvtWYb4
जोहान्स शहाब -

1
C / C ++ के रूप में फिर से लिया गया; यहाँ जावा पर विचार नहीं किया जा रहा है, C # समान है, लेकिन ऐसे कठोर परिभाषित मेमोरी शब्दार्थों का अभाव है।
टिम विलिसक्रॉफ्ट

1
@ ऑस्कर रेयेस कहते हैं कि आपके पास i वेरिएबल का उपयोग करते हुए दो धागे हैं। यदि थ्रेड केवल एक निश्चित बिंदु पर होने पर थ्रेड को बढ़ाता है और दूसरा केवल थ्रेड को कम करता है जब यह किसी अन्य बिंदु पर होता है, तो आपको थ्रेड सुरक्षा के बारे में चिंता करना होगा।
समोज

जवाबों:


157

आपने गलत सुना है। यह अच्छी तरह से हो सकता है कि "i++"एक विशिष्ट संकलक और विशिष्ट प्रोसेसर वास्तुकला के लिए थ्रेड-सुरक्षित है, लेकिन यह मानकों में अनिवार्य नहीं है। वास्तव में, चूंकि मल्टी-थ्रेडिंग आईएसओ सी या सी ++ मानकों (ए) का हिस्सा नहीं है , आप जो कुछ भी सोचते हैं, उसके आधार पर आप थ्रेड-सुरक्षित होने पर विचार नहीं कर सकते।

यह काफी व्यवहार्य है कि ++iएक मनमाना अनुक्रम करने के लिए संकलन कर सकता है जैसे:

load r0,[i]  ; load memory into reg 0
incr r0      ; increment reg 0
stor [i],r0  ; store reg 0 back to memory

जो मेरे (काल्पनिक) सीपीयू पर थ्रेड-सेफ नहीं होगा जिसमें मेमोरी-इन्क्रीमेंट निर्देश नहीं है। या यह स्मार्ट हो सकता है और इसे इसमें संकलित किया जा सकता है:

lock         ; disable task switching (interrupts)
load r0,[i]  ; load memory into reg 0
incr r0      ; increment reg 0
stor [i],r0  ; store reg 0 back to memory
unlock       ; enable task switching (interrupts)

जहां lockनिष्क्रिय करता है और unlockबाधित करता है। लेकिन, फिर भी, यह एक आर्किटेक्चर में थ्रेड-सुरक्षित नहीं हो सकता है जिसमें मेमोरी साझा करने वाले इन सीपीयू में से एक से अधिक है ( lockकेवल एक सीपीयू के लिए इंटरप्ट को अक्षम कर सकता है)।

स्वयं भाषा (या इसके लिए पुस्तकालय, अगर यह भाषा में निर्मित नहीं है) थ्रेड-सुरक्षित निर्माण प्रदान करेगा और आपको उन लोगों की बजाय अपनी समझ (या संभवतः गलतफहमी) का उपयोग करना चाहिए जो मशीन कोड उत्पन्न होंगे।

जावा synchronizedऔर pthread_mutex_lock()(कुछ ऑपरेटिंग सिस्टम के तहत सी / सी ++ के लिए उपलब्ध) जैसी चीजें हैं जिन्हें आपको (ए) में देखने की आवश्यकता है ।


(ए) यह सवाल C11 और C ++ 11 मानकों को पूरा करने से पहले पूछा गया था। उन पुनरावृत्तियों ने अब परमाणु डेटा प्रकार (हालांकि वे, और सामान्य रूप से थ्रेड वैकल्पिक हैं, कम से कम सी में) सहित भाषा विनिर्देशों में थ्रेडिंग समर्थन शुरू किया है।


8
+1 जोर देने के लिए कि यह एक मंच-विशिष्ट मुद्दा नहीं है, स्पष्ट उत्तर का उल्लेख नहीं है ...
RBerteig

2
congratz आपके C सिल्वर बैज के लिए :)
जोहान्स स्कैब - litb

मुझे लगता है कि आपको सटीक होना चाहिए कि कोई भी आधुनिक ओएस उपयोगकर्ता-मोड कार्यक्रमों को बाधित करने के लिए अधिकृत नहीं करता है, और pthread_mutex_lock () सी का हिस्सा नहीं है
बस्तियन लीनोर्ड

@ बास्टियन, कोई आधुनिक ओएस सीपीयू पर नहीं चल रहा होगा जिसमें मेमोरी वृद्धि का निर्देश नहीं था :-) लेकिन आपकी बात सी। के बारे में ली गई है
paxdiablo

5
@ बास्टियन: बुल। RISC प्रोसेसर में आमतौर पर मेमोरी बढ़ाने के निर्देश नहीं होते हैं। लोड / जोड़ / stor tripplet है कि आप कैसे करते हैं जैसे, PowerPC।
व्युत्पन्न

42

आप ++ i या i ++ के बारे में एक कंबल बयान नहीं कर सकते। क्यों? 32-बिट सिस्टम पर 64-बिट पूर्णांक बढ़ाने पर विचार करें। जब तक अंतर्निहित मशीन में क्वाड शब्द "लोड, इंक्रीमेंट, स्टोर" निर्देश नहीं होता है, तब तक उस मूल्य को बढ़ाते हुए कई निर्देशों की आवश्यकता होती है, जिनमें से किसी को थ्रेड संदर्भ स्विच द्वारा बाधित किया जा सकता है।

इसके अलावा, ++iहमेशा "एक को मूल्य में जोड़ें" नहीं है। सी जैसी भाषा में, एक पॉइंटर को बढ़ाना वास्तव में इंगित की गई चीज़ के आकार को जोड़ता है। यही है, अगर iएक 32-बाइट संरचना के लिए एक संकेतक है, तो ++i32 बाइट्स जोड़ता है। जबकि लगभग सभी प्लेटफार्मों में "मेमोरी एड्रेस पर इंक्रीमेंट वैल्यू" निर्देश है जो परमाणु है, सभी में परमाणु नहीं है "मेमोरी एड्रेस पर वैल्यू के लिए मनमाना मूल्य जोड़ें" निर्देश।


35
बेशक, यदि आप 32-बिट पूर्णांक को बोर करने के लिए खुद को सीमित नहीं करते हैं, तो C ++, ++ जैसी भाषा में, मैं वास्तव में एक webservice को कॉल कर सकता हूं जो एक डेटाबेस में एक मूल्य को अपडेट करता है।
ग्रहण

16

वे दोनों थ्रेड-असुरक्षित हैं।

एक CPU सीधे मेमोरी के साथ गणित नहीं कर सकता है। यह अप्रत्यक्ष रूप से मेमोरी से मूल्य लोड करके और सीपीयू रजिस्टरों के साथ गणित करता है।

मैं ++

register int a1, a2;

a1 = *(&i) ; // One cpu instruction: LOAD from memory location identified by i;
a2 = a1;
a1 += 1; 
*(&i) = a1; 
return a2; // 4 cpu instructions

++ मैं

register int a1;

a1 = *(&i) ; 
a1 += 1; 
*(&i) = a1; 
return a1; // 3 cpu instructions

दोनों मामलों के लिए, एक दौड़ की स्थिति होती है जिसके परिणामस्वरूप अप्रत्याशित मूल्य होता है।

उदाहरण के लिए, मान लेते हैं कि क्रमशः रजिस्टर a1, b1 का उपयोग करने वाले प्रत्येक के साथ दो समवर्ती ++ i सूत्र हैं। और, संदर्भ स्विचिंग को निम्न की तरह निष्पादित किया गया है:

register int a1, b1;

a1 = *(&i);
a1 += 1;
b1 = *(&i);
b1 += 1;
*(&i) = a1;
*(&i) = b1;

इसके परिणामस्वरूप, मैं i + 2 नहीं बनता, यह i + 1 हो जाता है, जो गलत है।

इसे मापने के लिए, मॉडेन सीपीयू कुछ प्रकार के LOCK प्रदान करते हैं, UNLOCK सीपीयू निर्देश अंतराल के दौरान एक संदर्भ स्विचिंग अक्षम है।

Win32 पर, थ्रेड-सेफ्टी के लिए ilock करने के लिए InterlockedIncrement () का उपयोग करें। यह म्यूटेक्स पर भरोसा करने की तुलना में बहुत तेज है।


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

1
LOCK और UNLOCK CPU निर्देशों का संदर्भ स्विच से कोई लेना-देना नहीं है। वे कैश लाइनों को लॉक करते हैं।
डेविड श्वार्ट्ज

11

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

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


वातावरण में गलत तरीके से जहां इंट एक्सेस (रीड / राइट) परमाणु है। ऐसे एल्गोरिदम हैं जो ऐसे वातावरण में काम कर सकते हैं, भले ही मेमोरी बाधाओं की कमी का मतलब हो सकता है कि आप कभी-कभी बासी डेटा पर काम कर रहे हैं।
MSalters

2
मैं सिर्फ यह कह रहा हूं कि परमाणुता थ्रेड-सेफनेस की गारंटी नहीं देती है। यदि आप लॉक-फ्री डेटास्ट्रक्चर या एल्गोरिदम डिजाइन करने के लिए पर्याप्त स्मार्ट हैं, तो आगे बढ़ें। लेकिन आपको अभी भी यह जानना होगा कि आपके कंपाइलर आपको क्या गारंटी देने वाले हैं।
ग्रहण

10

यदि आप C ++ में परमाणु वृद्धि चाहते हैं, तो आप C ++ 0x लाइब्रेरीज़ ( std::atomicडेटाटाइप) या TBB जैसी किसी चीज़ का उपयोग कर सकते हैं ।

एक समय था जब GNU कोडिंग दिशा-निर्देशों में कहा गया था कि डेटाटाइप्स को अपडेट करना जो एक शब्द में फिट होता है, "आमतौर पर सुरक्षित" था, लेकिन यह सलाह एसएमपी मशीनों के लिए गलत है, कुछ आर्किटेक्चर के लिए गलत है, और एक अनुकूलन कंपाइलर का उपयोग करते समय गलत है।


"अपडेटेड वन-वर्ड डेटाटाइप" टिप्पणी को स्पष्ट करने के लिए:

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

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

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

नोट मेरा मूल उत्तर यह भी कहा कि 64 बिट डेटा से निपटने में इंटेल 64 बिट आर्किटेक्चर टूट गया था। यह सच नहीं है, इसलिए मैंने जवाब संपादित किया, लेकिन मेरे संपादित दावा पावरपीसी चिप्स टूट गए थे। तात्कालिक मानों (अर्थात, स्थिरांक) को रजिस्टरों में पढ़ते समय यह सच है (देखें 2 सेक्शन और लिस्टिंग 4 के तहत "लोडिंग पॉइंटर्स" नामक दो सेक्शन)। लेकिन एक चक्र ( lmw) में मेमोरी से डेटा लोड करने के लिए एक निर्देश है , इसलिए मैंने अपने उत्तर के उस हिस्से को हटा दिया है।


पढ़ता है और लिखता है सबसे आधुनिक सीपीयू पर परमाणु अगर आपका डेटा स्वाभाविक रूप से संरेखित और सही आकार है, यहां तक ​​कि एसएमपी और अनुकूलन कंपाइलर्स के साथ भी। हालांकि, बहुत सी चीटियां हैं, खासकर 64 बिट मशीनों के साथ, इसलिए यह सुनिश्चित करना बोझिल हो सकता है कि आपका डेटा हर मशीन पर आवश्यकताओं को पूरा करता है।
डैन ओल्सन

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

4

X86 / Windows में C / C ++ पर, आपको यह नहीं मानना ​​चाहिए कि यह थ्रेड-सुरक्षित है। यदि आपको परमाणु संचालन की आवश्यकता है, तो आपको इंटरलॉकड इन्क्रिमेंट () और इंटरलॉकडड्रेमेंट () का उपयोग करना चाहिए ।


4

यदि आपकी प्रोग्रामिंग भाषा थ्रेड्स के बारे में कुछ नहीं कहती है, फिर भी एक मल्टीथ्रेड प्लेटफॉर्म पर चलती है, तो कोई कैसे कर सकता है भाषा सुरक्षित हो सकती है?

जैसा कि अन्य ने बताया: आपको प्लेटफ़ॉर्म विशिष्ट कॉल द्वारा चर तक किसी भी मल्टीथ्रेडेड पहुंच की रक्षा करने की आवश्यकता है।

प्लेटफ़ॉर्म की विशिष्टता को दूर करने वाले पुस्तकालय हैं, और आगामी सी ++ मानक ने इसे थ्रेड्स के साथ सामना करने के लिए मेमोरी मॉडल को अनुकूलित किया है (और इस प्रकार थ्रेड-सुरक्षा की गारंटी दे सकता है)।


4

यहां तक ​​कि अगर यह एक एकल विधानसभा निर्देश तक कम हो जाता है, तो मूल्य को सीधे मेमोरी में बढ़ाते हुए, यह अभी भी थ्रेड सुरक्षित नहीं है।

जब स्मृति में मूल्य में वृद्धि होती है, तो हार्डवेयर "रीड-मॉडिफ़ाइड-राइट" ऑपरेशन करता है: यह मेमोरी से मूल्य को पढ़ता है, इसे बढ़ाता है, और इसे वापस मेमोरी में लिखता है। X86 हार्डवेयर में मेमोरी को सीधे बढ़ाने का कोई तरीका नहीं है; RAM (और कैश) केवल मूल्यों को पढ़ने और संग्रहीत करने में सक्षम है, उन्हें संशोधित नहीं करता है।

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

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

ध्यान दें कि उपयोग volatileकरने से यहां मदद नहीं मिलती है; यह केवल संकलक को बताता है कि चर को बाहरी रूप से संशोधित किया जा सकता है और उस चर को पढ़ता है जिसे रजिस्टर या अनुकूलित में कैश नहीं किया जाना चाहिए। यह संकलक का उपयोग परमाणु आदिम नहीं करता है।

सबसे अच्छा तरीका परमाणु प्राइमेटिव्स का उपयोग करना है (यदि आपके कंपाइलर या लाइब्रेरीज़ उनके पास हैं), या असेंबली सीधे असेंबली में करें (सही परमाणु निर्देशों का उपयोग करके)।


2

यह कभी न मानें कि एक वृद्धि एक परमाणु ऑपरेशन के लिए संकलित होगी। InterlockedIncrement का उपयोग करें या जो भी समान कार्य आपके लक्ष्य मंच पर मौजूद हैं।

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


1
InterlockedIncrement () एक विंडोज फ़ंक्शन है; मेरे सभी लिनक्स बॉक्स और आधुनिक OS X मशीनें x64 आधारित हैं, इसलिए यह कहना कि InterlockedIncrement () x86 कोड की तुलना में 'अधिक पोर्टेबल' है, बल्कि बहुत ही सहज है।
पीट किर्कम

यह एक ही अर्थ में बहुत अधिक पोर्टेबल है कि सी असेंबली की तुलना में बहुत अधिक पोर्टेबल है। यहां लक्ष्य एक विशिष्ट प्रोसेसर के लिए विशिष्ट उत्पन्न विधानसभा पर भरोसा करने से खुद को प्रेरित करना है। यदि अन्य ऑपरेटिंग सिस्टम आपकी चिंता का विषय हैं तो InterlockedIncrement आसानी से लपेटा जाता है।
डैन ओल्सन

2

X86 पर इस असेंबली पाठ के अनुसार , आप किसी मेमोरी मेमोरी में एक रजिस्टर जोड़ सकते हैं , इसलिए संभवतः आपका कोड '++ i' ou 'i ++' को एटमॉली कर सकता है। लेकिन जैसा कि एक अन्य पोस्ट में कहा गया है, C asi '++' ओपेशन में एटमॉसिटी लागू नहीं करता है, इसलिए आप यह सुनिश्चित नहीं कर सकते हैं कि आपका कंपाइलर क्या उत्पन्न करेगा।


1

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

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

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


1

मुझे थ्रेड स्थानीय भंडारण में फेंक दें; यह परमाणु नहीं है, लेकिन यह तब कोई फर्क नहीं पड़ता।


1

AFAIK, C ++ मानक के अनुसार, पढ़ने / लिखने के लिए एक intपरमाणु हैं।

हालाँकि, यह सब उस अपरिभाषित व्यवहार से छुटकारा दिलाता है जो डेटा रेस से जुड़ा होता है।

लेकिन अभी भी एक डेटा दौड़ होगी अगर दोनों धागे वृद्धि के लिए प्रयास करते हैं i

इस परिदृश्य की कल्पना करें:

i = 0शुरू में चलो :

थ्रेड ए मेमोरी से मूल्य पढ़ता है और अपने स्वयं के कैश में संग्रहीत करता है। थ्रेड A 1 से मान बढ़ाता है।

थ्रेड बी मेमोरी से मूल्य पढ़ता है और अपने स्वयं के कैश में संग्रहीत करता है। थ्रेड बी 1 से मूल्य बढ़ाता है।

यदि यह सब एक धागा है जो आपको मिलेगा i = 2 याद होगा।

लेकिन दोनों धागे के साथ, प्रत्येक धागा अपने परिवर्तन लिखता है और इसलिए थ्रेड ए i = 1मेमोरी में वापस लिखता है , और थ्रेड बी i = 1मेमोरी में लिखता है।

यह अच्छी तरह से परिभाषित किया गया है, कोई आंशिक विनाश या निर्माण या किसी वस्तु को फाड़ने का नहीं है, लेकिन यह अभी भी एक डेटा दौड़ है।

परमाणु वृद्धि के क्रम में iआप उपयोग कर सकते हैं:

std::atomic<int>::fetch_add(1, std::memory_order_relaxed)

रिलैक्स्ड ऑर्डरिंग का उपयोग किया जा सकता है क्योंकि हमें परवाह नहीं है कि यह ऑपरेशन कहां होता है, हम इस बात का ध्यान रखते हैं कि वेतन वृद्धि परमाणु है।


0

आप कहते हैं "यह केवल एक निर्देश है, यह एक संदर्भ स्विच द्वारा निर्बाध होगा।" - यह सब अच्छी तरह से और एक सीपीयू के लिए अच्छा है, लेकिन दोहरे कोर सीपीयू के बारे में क्या? तब आपके पास वास्तव में एक ही चर पर बिना किसी संदर्भ स्विच के दो धागे हो सकते हैं।

भाषा को जाने बिना, इसका उत्तर है कि इसमें से बिल्ली का परीक्षण करना है।


4
आपको यह पता नहीं चल पाता है कि कोई चीज़ थ्रेड-सेफ है या नहीं - परीक्षण के मुद्दे एक लाख घटनाओं में से एक हो सकते हैं। आप इसे अपने प्रलेखन में देखते हैं। यदि यह आपके दस्तावेज़ द्वारा थ्रेडसेफ़ की गारंटी नहीं है, तो यह नहीं है।
ग्रहण

2
यहां @Josh से सहमत हैं। कुछ केवल थ्रेड-सुरक्षित है अगर यह अंतर्निहित कोड के विश्लेषण के माध्यम से गणितीय रूप से सिद्ध किया जा सकता है। परीक्षण की कोई भी राशि उस तक नहीं पहुंच सकती है।
रेक्स एम

यह उस अंतिम वाक्य तक एक महान जवाब था।
रोब के

0

मुझे लगता है कि यदि अभिव्यक्ति "i ++" केवल एक बयान में है, तो यह "++ i" के बराबर है, संकलक काफी स्मार्ट है ताकि एक लौकिक मूल्य नहीं रखा जा सके, आदि। इसलिए यदि आप उन्हें परस्पर विनिमय का उपयोग कर सकते हैं (अन्यथा आप जीत गए 'यह न पूछें कि कौन सा उपयोग करना है), इससे कोई फर्क नहीं पड़ता कि आप जो भी उपयोग करते हैं, वे लगभग एक जैसे हैं (सौंदर्यशास्त्र को छोड़कर)।

वैसे भी, भले ही इंक्रीमेंट ऑपरेटर परमाणु हो, लेकिन यह गारंटी नहीं देता है कि यदि आप सही ताले का उपयोग नहीं करते हैं तो बाकी की गणना सुसंगत होगी।

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


0

एक काउंटर के लिए, मैं तुलना और स्वैप मुहावरे का उपयोग करने की सलाह देता हूं जो गैर लॉकिंग और थ्रेड-सुरक्षित दोनों है।

यहाँ यह जावा में है:

public class IntCompareAndSwap {
    private int value = 0;

    public synchronized int get(){return value;}

    public synchronized int compareAndSwap(int p_expectedValue, int p_newValue){
        int oldValue = value;

        if (oldValue == p_expectedValue)
            value = p_newValue;

        return oldValue;
    }
}

public class IntCASCounter {

    public IntCASCounter(){
        m_value = new IntCompareAndSwap();
    }

    private IntCompareAndSwap m_value;

    public int getValue(){return m_value.get();}

    public void increment(){
        int temp;
        do {
            temp = m_value.get();
        } while (temp != m_value.compareAndSwap(temp, temp + 1));

    }

    public void decrement(){
        int temp;
        do {
            temp = m_value.get();
        } while (temp > 0 && temp != m_value.compareAndSwap(temp, temp - 1));

    }
}

एक test_and_set फ़ंक्शन के समान लगता है।
samoz

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