बहु-आयामी C या C ++ प्रोग्रामिंग में वाष्पशील को उपयोगी क्यों नहीं माना जाता है?


165

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

मेरी समझ यह है: किसी भी समय एक चर को नियंत्रित करने वाले कोड के टुकड़े के प्रवाह के बाहर बदला जा सकता है, उस चर को घोषित किया जाना चाहिए volatile। सिग्नल हैंडलर, I / O रजिस्टर, और दूसरे धागे द्वारा संशोधित चर ऐसी स्थितियों का गठन करते हैं।

इसलिए, यदि आपके पास एक वैश्विक इंट है foo, और fooएक थ्रेड द्वारा पढ़ा जाता है और दूसरे थ्रेड द्वारा एटोमिकली सेट किया जाता है (शायद एक उपयुक्त मशीन इंस्ट्रक्शन का उपयोग करके), रीडिंग थ्रेड इस स्थिति को उसी तरह से देखता है जैसे कि यह एक सिग्नल हैंडलर द्वारा घुमाए गए चर को देखता है या एक बाहरी हार्डवेयर स्थिति द्वारा संशोधित और इस प्रकार fooघोषित किया जाना चाहिए volatile(या, मल्टीथ्रेड स्थितियों के लिए, मेमोरी-फ़ेंस लोड के साथ पहुँचा जा सकता है, जो शायद एक बेहतर समाधान है)।

मैं कैसे और कहां गलत हूं?


7
सभी अस्थिर कहते हैं कि संकलक को अस्थिर चर तक पहुंच को कैश नहीं करना चाहिए। यह इस तरह की पहुंच को क्रमबद्ध करने के बारे में कुछ नहीं कहता है। यह चर्चा की गई है कि मुझे नहीं पता कि कितनी बार, और मुझे नहीं लगता कि यह प्रश्न उन चर्चाओं में कुछ भी जोड़ने वाला है।

4
और फिर भी, एक सवाल जो इसके लायक नहीं है, और यहां कई बार पूछा गया है इससे पहले कि ऊपर उठे। क्या आप कृपया ऐसा करना बंद कर देंगे।

14
@neil मैंने अन्य प्रश्नों की खोज की, और एक पाया, लेकिन किसी भी मौजूदा स्पष्टीकरण को मैंने किसी भी तरह से ट्रिगर नहीं किया जो मुझे वास्तव में समझने की आवश्यकता थी कि मैं गलत क्यों था। इस सवाल का ऐसा जवाब मिला है।
माइकल एकस्ट्रैंड

1
: सीपीयू डेटा का क्या किया (उनके कैश के माध्यम से) पर एक बड़ा में गहराई से अध्ययन के लिए बाहर की जाँच rdrop.com/users/paulmck/scalability/paper/whymb.2010.06.07c.pdf
Sassafras_wot

1
@curiousguy "मैं सी में मामला नहीं" के साथ इसका मतलब है, जहां यह हार्डवेयर रजिस्टरों आदि को लिखने के लिए इस्तेमाल किया जा सकता है, और मल्टीथ्रेडिंग के लिए उपयोग नहीं किया जाता है जैसे कि यह आमतौर पर जावा में उपयोग किया जाता है।
मॉन्स्टिएर

जवाबों:


213

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

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

साझा डेटा तक थ्रेड-सुरक्षित पहुंच के लिए, हमें एक गारंटी की आवश्यकता है:

  • पढ़ने / लिखने वास्तव में होता है (कि संकलक इसके बजाय एक रजिस्टर में मूल्य को संग्रहीत नहीं करेगा और बाद में मुख्य मेमोरी को अपडेट करना स्थगित कर देगा)
  • कि कोई पुनरावृत्ति नहीं होती है। मान लें कि हम एक volatileचर का उपयोग ध्वज के रूप में इंगित करने के लिए करते हैं कि कुछ डेटा पढ़ने के लिए तैयार है या नहीं। हमारे कोड में, हम केवल डेटा तैयार करने के बाद झंडा सेट करते हैं, इसलिए सभी ठीक दिखते हैं। लेकिन क्या होगा अगर निर्देशों को फिर से व्यवस्थित किया जाए ताकि झंडा पहले सेट हो जाए ?

volatileपहले बिंदु की गारंटी देता है। यह भी गारंटी देता है कि विभिन्न अस्थिर पढ़ने / लिखने के बीच कोई पुनरावृत्ति नहीं होती है । सभी volatileमेमोरी एक्सेस उस क्रम में होंगी, जिसमें वे निर्दिष्ट हैं। यही वह सब है जिसकी हमें आवश्यकता volatileहै: I / O रजिस्टर या मेमोरी-मैप हार्डवेयर में हेरफेर, लेकिन यह हमें मल्टीथ्रेडेड कोड में मदद नहीं करता है, जहां volatileऑब्जेक्ट का उपयोग केवल गैर-वाष्पशील डेटा तक पहुंच को सिंक्रनाइज़ करने के लिए किया जाता है। उन पहुँच अभी भी volatileलोगों के सापेक्ष reordered जा सकता है।

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

हालाँकि, मेमोरी बाधाएं यह भी सुनिश्चित करती हैं कि सभी लंबित रीड / राइट्स को निष्पादित होने पर निष्पादित किया जाता है, इसलिए यह प्रभावी रूप से हमें वह सब कुछ प्रदान करता है जिसकी हमें आवश्यकता है, volatileअनावश्यक बनाता है । हम volatileक्वालीफायर को पूरी तरह से हटा सकते हैं ।

C ++ 11 के बाद से, परमाणु चर ( std::atomic<T>) हमें प्रासंगिक गारंटी देते हैं।


5
@jbcreix: आप किस "के बारे में" पूछ रहे हैं? वाष्पशील या स्मृति बाधाएं? किसी भी मामले में, उत्तर बहुत अधिक समान है। वे दोनों को कंपाइलर और सीपीयू स्तर पर काम करना पड़ता है, क्योंकि वे प्रोग्राम के अवलोकनीय व्यवहार का वर्णन करते हैं --- इसलिए उन्हें यह सुनिश्चित करना होगा कि सीपीयू सब कुछ पुनः व्यवस्थित न करे, जिस व्यवहार की वे गारंटी देते हैं। लेकिन आप वर्तमान में पोर्टेबल थ्रेड सिंक्रोनाइज़ेशन नहीं लिख सकते हैं, क्योंकि मेमोरी बैरियर मानक C ++ का हिस्सा नहीं हैं (इसलिए वे पोर्टेबल नहीं हैं), और volatileउपयोगी होने के लिए पर्याप्त मजबूत नहीं है।
जलफ

4
एक MSDN उदाहरण यह करता है, और दावा करता है कि निर्देशों को एक अस्थिर पहुंच से आगे नहीं बढ़ाया
OJWI

27
@OJW: लेकिन Microsoft का कंपाइलर volatileएक पूर्ण मेमोरी बैरियर ( पुन: व्यवस्थित होने से रोकना) को फिर से परिभाषित करता है। यह मानक का हिस्सा नहीं है, इसलिए आप पोर्टेबल कोड में इस व्यवहार पर भरोसा नहीं कर सकते।
jalf

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

13
@ स्किज़: थ्रेड्स हमेशा C ++ 11 और C11 से पहले एक प्लेटफ़ॉर्म-निर्भर एक्सटेंशन हैं। मेरे ज्ञान के लिए, प्रत्येक C और C ++ वातावरण जो एक थ्रेडिंग एक्सटेंशन प्रदान करता है, एक "मेमोरी बैरियर" एक्सटेंशन भी प्रदान करता है। भले ही, volatileबहु-थ्रेडेड प्रोग्रामिंग के लिए हमेशा बेकार है। (विजुअल स्टूडियो, जहां अस्थिर को छोड़कर है स्मृति बाधा विस्तार।)
निमो

49

आप लिनक्स कर्नेल प्रलेखन से भी इस पर विचार कर सकते हैं ।

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

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

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

कर्नेल कोड के एक विशिष्ट ब्लॉक पर विचार करें:

spin_lock(&the_lock);
do_something_on(&shared_data);
do_something_else_with(&shared_data);
spin_unlock(&the_lock);

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

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

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

एक और स्थिति जहां किसी को अस्थिरता का उपयोग करने के लिए लुभाया जा सकता है, जब प्रोसेसर एक चर के मूल्य पर व्यस्त है। व्यस्त प्रतीक्षा करने का सही तरीका है:

while (my_variable != what_i_want)
    cpu_relax();

Cpu_relax () कॉल सीपीयू बिजली की खपत को कम कर सकता है या हाइपरथ्रेडेड ट्विन प्रोसेसर के लिए पैदावार कर सकता है; यह स्मृति अवरोधक के रूप में भी काम करता है, इसलिए, एक बार फिर, अस्थिर अनावश्यक है। बेशक, व्यस्त-प्रतीक्षा आम तौर पर एक असामाजिक कार्य है जिसके साथ शुरू करना है।

अभी भी कुछ दुर्लभ स्थितियाँ हैं जहाँ अस्थिर कर्नेल में समझ में आता है:

  • उपर्युक्त एक्सेसर फ़ंक्शंस आर्किटेक्चर पर अस्थिर का उपयोग कर सकते हैं जहां प्रत्यक्ष I / O मेमोरी एक्सेस काम करता है। अनिवार्य रूप से, प्रत्येक एक्सेसर कॉल अपने आप ही थोड़ा महत्वपूर्ण खंड बन जाता है और यह सुनिश्चित करता है कि एक्सेस प्रोग्रामर द्वारा अपेक्षित रूप से होता है।

  • इनलाइन असेंबली कोड जो मेमोरी को बदलता है, लेकिन जिसका कोई अन्य दृश्य दुष्प्रभाव नहीं है, जीसीसी द्वारा हटाए जा रहे जोखिम। अस्थिर कीवर्ड को asm स्टेटमेंट में जोड़ने से यह निष्कासन रोका जा सकेगा।

  • Jiffies वैरिएबल इस मायने में खास है कि इसे संदर्भित करने पर हर बार एक अलग मान हो सकता है, लेकिन इसे बिना किसी विशेष लॉकिंग के पढ़ा जा सकता है। इसलिए जिफ़ियाँ अस्थिर हो सकती हैं, लेकिन इस प्रकार के अन्य चर के जोड़ पर दृढ़ता से फेंक दिया जाता है। इस संबंध में जिफ़ी को एक "बेवकूफ विरासत" मुद्दा (लिनुस के शब्द) माना जाता है; इसे ठीक करने से ज्यादा परेशानी होती है।

  • सुसंगत स्मृति में डेटा संरचनाओं की ओर संकेत जो I / O उपकरणों द्वारा संशोधित किया जा सकता है, कभी-कभी, वैध रूप से अस्थिर हो सकते हैं। नेटवर्क एडेप्टर द्वारा उपयोग किया जाने वाला रिंग बफर, जहां वह एडेप्टर इंगित करता है कि यह इंगित करने के लिए कि कौन से डिस्क्रिप्टर संसाधित किए गए हैं, इस प्रकार की स्थिति का एक उदाहरण है।

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


3
@ कुरसीगुई: हाँ। Gcc.gnu.org/onbuildocs/gcc-4.0.4/gcc/Extended-Asm.html भी देखें ।
सेबेस्टियन मच

1
Spin_lock () एक नियमित फ़ंक्शन कॉल की तरह दिखता है। इसके बारे में क्या विशेष है कि संकलक इसे विशेष रूप से व्यवहार करेगा ताकि उत्पन्न कोड साझा करें_डेटा के किसी भी मूल्य को "भूल" जाएगा जिसे स्पिन_लॉक () से पहले पढ़ा गया है और एक रजिस्टर में संग्रहीत किया गया है ताकि मूल्य को नए सिरे से पढ़ा जा सके do_something_on () स्पिन_लॉक () के बाद?
संकुचित

1
@underscore_d मेरी बात यह है कि मैं फ़ंक्शन नाम spin_lock () से नहीं बता सकता कि यह कुछ विशेष करता है। मुझे नहीं पता कि इसमें क्या है। विशेष रूप से, मुझे नहीं पता कि कार्यान्वयन में ऐसा क्या है जो कंपाइलर को बाद में पढ़ने वाले को अनुकूलित करने से रोकता है।
संकुचित

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

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

11

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

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

इसलिए अस्थिरता ठीक है, जब तक आप यह ध्यान रखते हैं कि अस्थिर चर में 'देखे गए' परिवर्तन ठीक उसी समय पर नहीं हो सकते हैं जब आप सोचते हैं कि वे करेंगे। विशेष रूप से, थ्रेड के पार ऑपरेशन को सिंक्रनाइज़ करने या ऑर्डर करने के तरीके के रूप में अस्थिर चर का उपयोग करने की कोशिश न करें, क्योंकि यह मज़बूती से काम नहीं करेगा।

व्यक्तिगत रूप से, वाष्पशील ध्वज के लिए मेरा मुख्य (केवल?) उपयोग "प्लीजगोवे नाउ" बूलियन के रूप में है। यदि मेरे पास एक श्रमिक धागा है जो लगातार लूप करता है, तो मेरे पास लूप के प्रत्येक पुनरावृत्ति पर वाष्पशील बूलियन की जांच करना होगा, और यदि बूलियन कभी सच है तो बाहर निकलें। मुख्य थ्रेड तब बूलियन को सही पर सेट करके श्रमिक थ्रेड को सुरक्षित रूप से साफ़ कर सकता है, और तब preadread_join () को कॉल कर सकता है जब तक कि वर्कर थ्रेड चला नहीं जाता।


2
आपका बूलियन झंडा शायद असुरक्षित है। आप इस बात की गारंटी कैसे देते हैं कि कार्यकर्ता अपना कार्य पूरा करता है, और यह कि झंडा तब तक दायरे में रहता है जब तक कि उसे पढ़ा नहीं जाता है (यदि यह पढ़ा गया है)? यह संकेतों के लिए एक नौकरी है। वाष्पशील सरल स्पिनलॉक को लागू करने के लिए अच्छा है यदि कोई म्यूटेक्स शामिल नहीं है, क्योंकि उपनाम सुरक्षा का मतलब है कि कंपाइलर मानता है mutex_lock(और प्रत्येक अन्य लाइब्रेरी फ़ंक्शन) ध्वज चर की स्थिति को बदल सकता है।
पोटाटोज़वाटर

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

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

10
@ जेरेमी, आप व्यवहार में सही हैं लेकिन सैद्धांतिक रूप से यह अभी भी टूट सकता है। एक दो कोर सिस्टम पर एक कोर लगातार आपके कार्यकर्ता थ्रेड को निष्पादित कर रहा है। अन्य कोर बूल को सही बनाता है। हालांकि इस बात की कोई गारंटी नहीं है कि मजदूर के धागे का कोर कभी भी उस बदलाव को देखेगा, अर्थात यह कभी भी बंद नहीं हो सकता है, हालांकि यह बार-बार बूल की जाँच करता है। यह व्यवहार c ++ 0x, java और c # मेमोरी मॉडल द्वारा अनुमत है। व्यवहार में यह कभी व्यस्त नहीं होगा क्योंकि व्यस्त धागे में कहीं-कहीं एक मेमोरी बैरियर लगा होता है, जिसके बाद यह बूल में परिवर्तन को देखेगा।
deft_code

4
POSIX सिस्टम लें, वास्तविक समय निर्धारण नीति का उपयोग करें, सिस्टम में SCHED_FIFOअन्य प्रक्रियाओं / थ्रेड्स की तुलना में उच्च स्थिर प्राथमिकता, पर्याप्त कोर, पूरी तरह से संभव होना चाहिए। लिनक्स में आप यह निर्दिष्ट कर सकते हैं कि वास्तविक समय की प्रक्रिया CPU समय का 100% उपयोग कर सकती है। यदि कोई उच्च प्राथमिकता थ्रेड / प्रक्रिया नहीं है और I / O द्वारा कभी ब्लॉक नहीं किया जाता है तो वे संदर्भ स्विच नहीं करेंगे। लेकिन मुद्दा यह है कि C / C ++ volatileका मतलब उचित डेटा शेयरिंग / सिंक्रोनाइज़ेशन शब्दार्थ को लागू करना नहीं है। मैं यह साबित करने के लिए विशेष मामलों की खोज कर रहा हूं कि गलत कोड शायद कभी-कभी काम कर सकता है बेकार व्यायाम।
FooF

7

volatileएक स्पिनक म्यूटेक्स के मूल निर्माण को लागू करने के लिए (यद्यपि अपर्याप्त) है, लेकिन एक बार आपके पास (या कुछ बेहतर) होने के बाद, आपको दूसरे की आवश्यकता नहीं है volatile

मल्टीथ्रेडेड प्रोग्रामिंग का विशिष्ट तरीका मशीन स्तर पर प्रत्येक साझा चर की रक्षा करना नहीं है, बल्कि गार्ड चर पेश करना है जो प्रोग्राम प्रवाह को निर्देशित करते हैं। इसके बजाय volatile bool my_shared_flag;आपके पास होना चाहिए

pthread_mutex_t flag_guard_mutex; // contains something volatile
bool my_shared_flag;

न केवल यह "कठिन भाग," को मूल रूप से आवश्यक रूप से समझाया जाता है: C में एक mxx को लागू करने के लिए आवश्यक परमाणु संचालन शामिल नहीं है ; यह केवल साधारण संचालन के volatileबारे में अतिरिक्त गारंटी देता है ।

अब आपके पास कुछ इस तरह है:

pthread_mutex_lock( &flag_guard_mutex );
my_local_state = my_shared_flag; // critical section
pthread_mutex_unlock( &flag_guard_mutex );

pthread_mutex_lock( &flag_guard_mutex ); // may alter my_shared_flag
my_shared_flag = ! my_shared_flag; // critical section
pthread_mutex_unlock( &flag_guard_mutex );

my_shared_flag अस्थिर होने के बावजूद अस्थिर होने की आवश्यकता नहीं है, क्योंकि

  1. एक और सूत्र की पहुंच है।
  2. मतलब इसका संदर्भ कुछ समय ( &ऑपरेटर के साथ ) लिया गया होगा ।
    • (या एक संदर्भ युक्त संरचना में लिया गया था)
  3. pthread_mutex_lock एक पुस्तकालय समारोह है।
  4. मतलब कंपाइलर यह नहीं बता सकता है कि pthread_mutex_lockकिसी तरह से वह संदर्भ प्राप्त करता है या नहीं ।
  5. मतलब संकलक को यह मानना चाहिए कि pthread_mutex_lockसाझा ध्वज को संशोधित करता है !
  6. इसलिए चर को मेमोरी से पुनः लोड किया जाना चाहिए। volatile, जबकि इस संदर्भ में सार्थक, विलुप्त है।

6

आपकी समझ वास्तव में गलत है।

संपत्ति, जिसमें अस्थिर चर हैं, "इस चर से पढ़ता है और लिखता है, कार्यक्रम के विचारशील व्यवहार का हिस्सा है"। इसका मतलब है कि यह कार्यक्रम काम करता है (उचित हार्डवेयर दिया गया है):

int volatile* reg=IO_MAPPED_REGISTER_ADDRESS;
*reg=1; // turn the fuel on
*reg=2; // ignition
*reg=3; // release
int x=*reg; // fire missiles

समस्या यह है, यह वह संपत्ति नहीं है जिसे हम धागा-सुरक्षित कुछ भी चाहते हैं।

उदाहरण के लिए, एक थ्रेड-सुरक्षित काउंटर सिर्फ होगा (लिनक्स-कर्नेल-जैसे कोड, c ++ 0x समतुल्य नहीं जानते):

atomic_t counter;

...
atomic_inc(&counter);

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

atomic_inc(&counter);
atomic_inc(&counter);

अभी भी अनुकूलित किया जा सकता है

atomically {
  counter+=2;
}

यदि ऑप्टिमाइज़र पर्याप्त स्मार्ट है (यह कोड के शब्दार्थ को नहीं बदलता है)।


6

आपके डेटा को समवर्ती वातावरण के अनुरूप होने के लिए आपको आवेदन करने के लिए दो शर्तों की आवश्यकता है:

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

2) संगति अर्थात ओप / लिखने के क्रम को कई समवर्ती वातावरणों के बीच समान रूप से देखा जाना चाहिए - जैसे धागे, मशीनें आदि।

वाष्पशील फिट बैठता है न तो ऊपर - या अधिक विशेष रूप से, c या c ++ मानक के रूप में कैसे वाष्पशील व्यवहार करना चाहिए न तो ऊपर भी शामिल है।

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

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

c # और java AFAIK इसे 1 (2) और 2) वाष्पशील पालन करते हुए इसका निवारण करते हैं, हालांकि c / c ++ कंपाइलर के लिए भी ऐसा नहीं कहा जा सकता है, इसलिए मूल रूप से इसके साथ ऐसा करें जैसा कि आप फिट देखते हैं।

इस विषय पर कुछ और गहराई से (हालांकि निष्पक्ष नहीं) चर्चा पढ़ें


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

जब व्यक्ति पढ़ते हैं और स्मृति को बाधित और गैर-परमाणु लिखते हैं? क्या कोई लाभ है?
बैटब्रेट

5

Comp.programming.threads FAQ में डेव ब्यूटेनहोफ़ द्वारा एक क्लासिक विवरण दिया गया है:

Q56: मुझे साझा चर VOLATILE घोषित करने की आवश्यकता क्यों नहीं है?

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

कुछ अर्थों में यह सच है, यदि कंपाइलर चर के संबंधित स्कोप और pthread_cond_wait (या pthread_mutex_lock) फ़ंक्शन के बारे में पर्याप्त जानता है। व्यवहार में, अधिकांश संकलक किसी कॉल के पार वैश्विक डेटा की प्रतियों को बाहरी फ़ंक्शन में रखने का प्रयास नहीं करेंगे, क्योंकि यह जानना बहुत कठिन है कि क्या रूटीन में किसी तरह डेटा के पते तक पहुंच हो सकती है।

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

इसलिए यदि आपका प्रोग्राम टूट जाता है क्योंकि आपने अस्थिर का उपयोग नहीं किया है, तो यह एक बग है। यह C में बग, या थ्रेड लाइब्रेरी में बग, या कर्नेल में बग नहीं हो सकता है। लेकिन यह एक सिस्टम बग है, और उन घटकों में से एक या अधिक को इसे ठीक करने के लिए काम करना होगा।

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

/ --- [डेव ब्यूटेनहोफ़] ----------------------- [butenhof@zko.dec.com] --- \
| डिजिटल उपकरण निगम 110 थूक ब्रूक Rd ZKO2-3 / Q18 |
| 603.881.2218, FAX 603.881.0120 नाशुआ NH 03062-2698 |
----------------- [कंसीडर के माध्यम से बेहतर जीवनयापन] ---------------- /

मिस्टर बुटेनहोफ़ ने इस यूनेट पोस्ट में एक ही मैदान को कवर किया है :

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

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

यह सब C ++ पर समान रूप से लागू होता है।


लिंक टूट गया है; यह अब इंगित नहीं करता है कि आप क्या कहना चाहते थे। पाठ के बिना, इसका एक तरह का अर्थहीन उत्तर।
jww

3

यह वह सब है जो "अस्थिर" कर रहा है: "अरे संकलक, यह चर एटी किसी भी समय (किसी भी घड़ी की टिक पर) को बदल सकता है, भले ही कोई स्थानीय निर्देश उस पर कार्य कर रहे हों। इस मूल्य को एक रजिस्टर में कैश न करें।"

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

आप "डॉ। डॉब्स" जैसे लेखों का सामना कर सकते हैं जो मल्टी-थ्रेडेड प्रोग्रामिंग के लिए कुछ रामबाण के रूप में अस्थिर हैं। उनका दृष्टिकोण पूरी तरह से योग्यता से रहित नहीं है, लेकिन इसमें किसी वस्तु के उपयोगकर्ताओं को इसकी थ्रेड-सुरक्षा के लिए जिम्मेदार बनाने का मूल दोष है, जो कि एन्कैप्सुलेशन के अन्य उल्लंघनों के समान मुद्दों को रखता है।


3

मेरे पुराने सी मानक के अनुसार, "क्या एक ऐसी वस्तु तक पहुंच है, जिसमें अस्थिर-योग्य प्रकार कार्यान्वयन-परिभाषित है" । तो सी संकलक लेखकों के लिए "अस्थिर" का अर्थ हो सकता है "बहु-प्रक्रिया वातावरण में धागा सुरक्षित पहुंच" । लेकिन उन्होंने ऐसा नहीं किया।

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

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


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

अधिकांश प्लेटफार्मों पर, यह पहचानना काफी आसान volatileहोगा कि किसी को OS लिखने की अनुमति देने के लिए क्या करना चाहिए जो हार्डवेयर-निर्भर लेकिन संकलक-स्वतंत्र है। आवश्यक है कि प्रोग्रामर कार्यान्वयन-निर्भर सुविधाओं का उपयोग करने के बजाय volatileकाम करने के लिए आवश्यक मानक बनाने के उद्देश्य को कम करते हैं।
Supercat
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.