C # में अस्थिर कीवर्ड का उपयोग कब किया जाना चाहिए?


299

क्या कोई C # में अस्थिर कीवर्ड का अच्छा विवरण दे सकता है? यह कौन सी समस्याओं को हल करता है और कौन सा नहीं है? किन मामलों में यह मुझे लॉकिंग के उपयोग से बचाएगा?


6
आप लॉकिंग के उपयोग पर बचत क्यों करना चाहते हैं? अनियंत्रित ताले आपके कार्यक्रम में कुछ नैनोसेकंड जोड़ते हैं। क्या आप वास्तव में कुछ नैनोसेकंड का खर्च नहीं उठा सकते हैं?
एरिक लिपर्ट

जवाबों:


273

मुझे नहीं लगता कि एरिक लिपर्ट (मूल में जोर) की तुलना में इसका जवाब देने के लिए एक बेहतर व्यक्ति है :

C # में, "वाष्पशील" का अर्थ केवल यह नहीं है कि "सुनिश्चित करें कि कंपाइलर और जिटर इस चर पर कैशिंग ऑप्टिमाइज़ेशन का कोई कोड पुन: व्यवस्थित या पंजीकृत नहीं करते हैं"। इसका मतलब यह भी है कि "प्रोसेसर को यह बताने के लिए कि उसे जो कुछ भी करना है, उसे यह सुनिश्चित करने के लिए कि मुझे नवीनतम मूल्य पढ़ना है, भले ही इसका मतलब है कि अन्य प्रोसेसर को रोकना और उन्हें अपने कैश के साथ मुख्य मेमोरी को सिंक्रनाइज़ करना है"।

दरअसल, वह आखिरी सा झूठ है। वाष्पशील का सच्चा शब्दार्थ पढ़ता है और लिखता है कि मैं यहाँ उल्लिखित की तुलना में बहुत अधिक जटिल हूँ; वास्तव में वे वास्तव में यह गारंटी नहीं देते हैं कि प्रत्येक प्रोसेसर बंद हो जाता है कि वह क्या कर रहा है और मुख्य मेमोरी से / के लिए कैश अपडेट करता है। इसके बजाय, वे इस बारे में कमजोर गारंटी प्रदान करते हैं कि पढ़ने और लिखने से पहले मेमोरी कैसे एक्सेस करती है और एक दूसरे के संबंध में आदेश के साथ देखा जा सकता है । कुछ संचालन जैसे कि एक नया धागा बनाना, एक लॉक में प्रवेश करना, या विधियों में से किसी एक इंटरलॉक परिवार का उपयोग करके ऑर्डर के अवलोकन के बारे में मजबूत गारंटी देता है। यदि आप अधिक विवरण चाहते हैं, तो C # 4.0 विनिर्देशन के खंड 3.10 और 10.5.3 पढ़ें।

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

आगे पढ़ने के लिए देखें:


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

3
@PaulEaster तथ्य यह है कि यह doulbe- जाँच लॉकिंग (आमतौर पर सिंगलटन पैटर्न में) के लिए इस्तेमाल किया जा सकता है इसका मतलब यह नहीं है कि यह होना चाहिए । .NET मेमोरी मॉडल पर भरोसा करना शायद एक बुरा अभ्यास है - आपको इसके बजाय ECMA मॉडल पर भरोसा करना चाहिए। उदाहरण के लिए, आप एक दिन मोनो में पोर्ट करना चाह सकते हैं, जिसमें एक अलग मॉडल हो सकता है। मुझे यह भी समझना है कि विभिन्न हार्डवेयर आर्किटेकिलर चीजें बदल सकते हैं। अधिक जानकारी के लिए देखें: stackoverflow.com/a/7230679/67824 । बेहतर सिंगलटन विकल्पों के लिए (सभी .NET संस्करणों के लिए) देखें: csharpindepth.com/articles/general/singleton.aspx
Schneider

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

3
क्या इसका मतलब यह है कि ताले और अस्थिर चर निम्न अर्थों में पारस्परिक रूप से अनन्य हैं: अगर मैंने कुछ चर के चारों ओर ताले का उपयोग किया है, तो उस चर को अब अस्थिर घोषित करने की कोई आवश्यकता नहीं है?
जियोर्जिम

4
@ जियार्गी हाँ - द्वारा की गई स्मृति बाधाएं volatileलॉक के आधार पर होंगी
ओहद श्नाइडर

54

यदि आप इस बारे में थोड़ा और अधिक तकनीकी प्राप्त करना चाहते हैं कि वाष्पशील कीवर्ड क्या करता है, तो निम्नलिखित कार्यक्रम पर विचार करें (मैं DevStudio 2005 का उपयोग कर रहा हूं):

#include <iostream>
void main()
{
  int j = 0;
  for (int i = 0 ; i < 100 ; ++i)
  {
    j += i;
  }
  for (volatile int i = 0 ; i < 100 ; ++i)
  {
    j += i;
  }
  std::cout << j;
}

मानक अनुकूलित (रिलीज़) संकलक सेटिंग्स का उपयोग करके, कंपाइलर निम्नलिखित कोडांतरक (IA32) बनाता है:

void main()
{
00401000  push        ecx  
  int j = 0;
00401001  xor         ecx,ecx 
  for (int i = 0 ; i < 100 ; ++i)
00401003  xor         eax,eax 
00401005  mov         edx,1 
0040100A  lea         ebx,[ebx] 
  {
    j += i;
00401010  add         ecx,eax 
00401012  add         eax,edx 
00401014  cmp         eax,64h 
00401017  jl          main+10h (401010h) 
  }
  for (volatile int i = 0 ; i < 100 ; ++i)
00401019  mov         dword ptr [esp],0 
00401020  mov         eax,dword ptr [esp] 
00401023  cmp         eax,64h 
00401026  jge         main+3Eh (40103Eh) 
00401028  jmp         main+30h (401030h) 
0040102A  lea         ebx,[ebx] 
  {
    j += i;
00401030  add         ecx,dword ptr [esp] 
00401033  add         dword ptr [esp],edx 
00401036  mov         eax,dword ptr [esp] 
00401039  cmp         eax,64h 
0040103C  jl          main+30h (401030h) 
  }
  std::cout << j;
0040103E  push        ecx  
0040103F  mov         ecx,dword ptr [__imp_std::cout (40203Ch)] 
00401045  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (402038h)] 
}
0040104B  xor         eax,eax 
0040104D  pop         ecx  
0040104E  ret              

आउटपुट को देखते हुए, कंपाइलर ने j वैरिएबल के मान को स्टोर करने के लिए ecx रजिस्टर का उपयोग करने का निर्णय लिया है। गैर-वाष्पशील लूप (पहले) के लिए संकलक ने मुझे ईएक्सएक्स रजिस्टर में सौंपा है। काफी सरल। हालांकि दिलचस्प बिट्स के एक जोड़े हैं - लीक ईबेक्स, [ईबेक्स] निर्देश प्रभावी रूप से एक मल्टीबाइट एनओपी निर्देश है ताकि लूप 16 बाइट संरेखित मेमोरी पते पर कूदता है। अन्य ईडीएक्स अनुदेश का उपयोग करने के बजाय लूप काउंटर को बढ़ाने के लिए edx का उपयोग है। ऐड reg, reg इंस्ट्रक्शंस में inc reg निर्देश की तुलना में कुछ IA32 कोर पर कम विलंबता है, लेकिन उच्च विलंबता कभी नहीं है।

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

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

इसलिए, वाष्पशील कीवर्ड केवल संरेखित डेटा के लिए अच्छा है जो कि मूल रजिस्टरों के आकार से कम या बराबर है, जैसे कि ऑपरेशन हमेशा परमाणु होते हैं।

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

यदि आप बड़े डेटा को संभाल रहे हैं या आपके पास कई सीपीयू हैं तो आपको डेटा एक्सेस को ठीक से संभालने के लिए उच्च स्तर (ओएस) लॉकिंग सिस्टम की आवश्यकता होगी।


यह C ++ है लेकिन सिद्धांत C # पर लागू होता है।
स्किज़

6
एरिक लिपर्ट लिखते हैं कि C ++ में अस्थिरता केवल कंपाइलर को कुछ अनुकूलन करने से रोकती है, जबकि C # वाष्पशील में अतिरिक्त कोर / प्रोसेसर के बीच कुछ संचार करता है ताकि यह सुनिश्चित किया जा सके कि नवीनतम मान पढ़ा जा सके।
पीटर ह्यूबर

44

यदि आप .NET 1.1 का उपयोग कर रहे हैं, तो डबल चेक लॉकिंग करते समय अस्थिर कीवर्ड की आवश्यकता होती है। क्यों? .NET 2.0 से पहले, निम्न परिदृश्य एक गैर-नल तक पहुँचने के लिए दूसरा धागा पैदा कर सकता है, फिर भी पूरी तरह से निर्मित वस्तु नहीं:

  1. थ्रेड 1 पूछता है कि क्या एक चर शून्य है। //if(this.foo == अशक्त)
  2. थ्रेड 1 निर्धारित करता है कि चर शून्य है, इसलिए एक लॉक में प्रवेश करता है। //lock(this.bar)
  3. थ्रेड 1 पूछता है कि क्या चर शून्य है। //if(this.foo == अशक्त)
  4. थ्रेड 1 अभी भी निर्धारित करता है कि चर शून्य है, इसलिए यह एक निर्माता को बुलाता है और चर को मान प्रदान करता है। //this.foo = new Foo ();

.NET 2.0 से पहले, इस। को फू का नया उदाहरण सौंपा जा सकता है, इससे पहले कि कंस्ट्रक्टर समाप्त हो रहा था। इस स्थिति में, एक दूसरा धागा आ सकता है (थू के निर्माता को थ्रेड 1 के कॉल के दौरान) और निम्नलिखित का अनुभव कर सकते हैं:

  1. थ्रेड 2 पूछता है कि क्या चर शून्य है। //if(this.foo == अशक्त)
  2. थ्रेड 2 निर्धारित करता है कि चर शून्य नहीं है, इसलिए इसका उपयोग करने की कोशिश करता है। //this.foo.MakeFoo ()

.NET 2.0 से पहले, आप इस समस्या के बारे में जानने के लिए इसे अस्थिर होने की घोषणा कर सकते हैं। .NET 2.0 के बाद से, आपको अब डबल चेक किए गए लॉकिंग को पूरा करने के लिए अस्थिर कीवर्ड का उपयोग करने की आवश्यकता नहीं है।

विकिपीडिया वास्तव में डबल चेक्ड लॉकिंग पर एक अच्छा लेख है, और इस विषय पर संक्षिप्त रूप से स्पर्श करता है: http://en.wikipedia.org/wiki/Double-checked_locking


2
यह वही है जो मैं एक विरासत कोड में देखता हूं और इसके बारे में सोच रहा था। यही कारण है कि मैंने एक गहन शोध शुरू किया। धन्यवाद!
पीटर पोर्फि

1
मुझे समझ में नहीं आता कि थ्रेड 2 किस तरह से मान प्रदान करेगा foo? क्या थ्रेड 1 लॉकिंग नहीं है this.barऔर इसलिए केवल थ्रेड 1 एक समय में givne बिंदु पर foo को इनिशियलाइज़ करने में सक्षम होगा? मेरा मतलब है, आप लॉक को फिर से जारी करने के बाद मूल्य की जांच करते हैं, जब भी वैसे भी थ्रेड से नया मान होना चाहिए 1
गिल्मिशल

24

कभी-कभी, कंपाइलर एक फ़ील्ड को ऑप्टिमाइज़ करेगा और इसे स्टोर करने के लिए एक रजिस्टर का उपयोग करेगा। यदि थ्रेड 1 फ़ील्ड को लिखता है और कोई अन्य थ्रेड इसे एक्सेस करता है, क्योंकि अपडेट को एक रजिस्टर (और मेमोरी में नहीं) में संग्रहीत किया गया था, तो 2 थ्रेड को बासी डेटा मिलेगा।

आप वाष्पशील कीवर्ड के बारे में सोच सकते हैं कि कंपाइलर "मैं चाहता हूं कि आप इस मान को मेमोरी में स्टोर करें"। यह गारंटी देता है कि 2 थ्रेड नवीनतम मान प्राप्त करता है।


21

से MSDN : अस्थिर संशोधक आम तौर पर एक क्षेत्र है कि serialize उपयोग करने के लिए ताला बयान का उपयोग किए बिना एक से अधिक थ्रेड द्वारा पहुँचा है के लिए प्रयोग किया जाता है। वाष्पशील संशोधक का उपयोग यह सुनिश्चित करता है कि एक धागा दूसरे धागे द्वारा लिखे गए सबसे अद्यतित मूल्य को पुनः प्राप्त करता है।


13

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

आप स्पष्ट रूप से कुछ अनुकूलन खो देते हैं, लेकिन यह कोड को अधिक सरल रखता है।


3

जॉयदीप कांजीलाल का यह लेख मुझे बहुत मददगार लगा!

When you mark an object or a variable as volatile, it becomes a candidate for volatile reads and writes. It should be noted that in C# all memory writes are volatile irrespective of whether you are writing data to a volatile or a non-volatile object. However, the ambiguity happens when you are reading data. When you are reading data that is non-volatile, the executing thread may or may not always get the latest value. If the object is volatile, the thread always gets the most up-to-date value

मैं इसे केवल संदर्भ के लिए यहाँ छोड़ दूँगा


0

संकलक कभी-कभी इसे अनुकूलित करने के लिए कोड में बयानों का क्रम बदलता है। आम तौर पर यह एकल-थ्रेडेड वातावरण में कोई समस्या नहीं है, लेकिन यह बहु-थ्रेडेड वातावरण में एक समस्या हो सकती है। निम्न उदाहरण देखें:

 private static int _flag = 0;
 private static int _value = 0;

 var t1 = Task.Run(() =>
 {
     _value = 10; /* compiler could switch these lines */
     _flag = 5;
 });

 var t2 = Task.Run(() =>
 {
     if (_flag == 5)
     {
         Console.WriteLine("Value: {0}", _value);
     }
 });

यदि आप t1 और t2 चलाते हैं, तो आप परिणाम के रूप में कोई आउटपुट या "मान: 10" की अपेक्षा नहीं करेंगे। यह हो सकता है कि संकलक t1 फ़ंक्शन के अंदर स्विच करता है। यदि t2 तब निष्पादित होता है, तो यह हो सकता है कि _flag का मान 5 है, लेकिन _value में 0. है इसलिए अपेक्षित तर्क को तोड़ा जा सकता है।

इसे ठीक करने के लिए आप वाष्पशील कीवर्ड का उपयोग कर सकते हैं जिसे आप फ़ील्ड पर लागू कर सकते हैं। यह कथन कंपाइलर ऑप्टिमाइज़ेशन को अक्षम करता है ताकि आप सही क्रम को कोड में लागू कर सकें।

private static volatile int _flag = 0;

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


2
आपका उदाहरण वास्तव में बुरा है। प्रोग्रामर को कभी भी t2 टास्क में _flag की वैल्यू पर इस बात की उम्मीद नहीं रखनी चाहिए कि t1 का कोड पहले लिखा गया है। पहले लिखा गया! = पहले निष्पादित। इससे कोई फर्क नहीं पड़ता कि कंपाइलर उन दो लाइनों को t1 में स्विच करता है। यहां तक ​​कि अगर कंपाइलर ने उन स्टेटमेंट्स को स्विच नहीं किया है, तो आपके कंसोल। अन्य शाखा में अभी भी निष्पादित हो सकते हैं, यहां तक ​​कि _flag पर अस्थिर कीवर्ड के साथ भी।
13 जुलाई को जकोथशेड्स

@ जजाकोथशेड्स, आप सही हैं, मैंने अपना उत्तर संपादित कर दिया है। मेरा मुख्य विचार यह दिखाना था कि जब हम t1 और t2 को एक साथ चलाते हैं, तो अपेक्षित तर्क को तोड़ा जा सकता है
अलायसी मनिउक

0

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


-4

कई धागे एक चर का उपयोग कर सकते हैं। नवीनतम अद्यतन चर पर होगा

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