2009-01-19 7 views
24

यह प्रश्न दौड़-परिस्थितियों, परमाणुता के बारे में, या आपको अपने कोड में ताले का उपयोग क्यों करना चाहिए। मैं पहले से ही उन लोगों के बारे में जानता हूँ।क्या सी # थ्रेड वास्तव में एक मूल्य कैश कर सकता है और अन्य धागे पर उस मान में परिवर्तनों को अनदेखा कर सकता है?

अद्यतन: मेरा प्रश्न "अस्थिर स्मृति के साथ अजीबता नहीं है" (मुझे पता है कि यह करता है), मेरा सवाल है ".NET रनटाइम सार नहीं है जो आप इसे कभी नहीं देख पाएंगे"।

http://www.yoda.arachsys.com/csharp/threads/volatility.shtml और Is a string property itself threadsafe?

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

class BackgroundTaskDemo 
{ 
    private bool stopping = false; 

    static void Main() 
    { 
     BackgroundTaskDemo demo = new BackgroundTaskDemo(); 
     new Thread(demo.DoWork).Start(); 
     Thread.Sleep(5000); 
     demo.stopping = true; 
    } 

    static void DoWork() 
    { 
     while (!stopping) 
     { 
       // Do something here 
     } 
    } 
} 
+0

मैं मानता हूं कि ऐसा लगता है कि यह हमेशा काम करना चाहिए। शायद पीईएक्स इसे तोड़ने का एक तरीका ढूंढ सकता है। –

+0

वह संकलित भी नहीं करेगा; उदाहरण शायद समस्या-क्षेत्र के करीब है यदि आप सबकुछ स्थिर बनाते हैं (अन्यथा भरोसा अनुकूलन को रोक सकता है, * यदि * जेआईटी करता है)। –

+0

कामकाजी काउंटर उदाहरण के साथ अद्यतन ;-p –

उत्तर

47

बिंदु है: यह काम हो सकता है, लेकिन ऐसा नहीं कल्पना से काम करने के लिए गारंटी है। क्या लोगों को आम तौर के बाद कर रहे हैं कोड है कि सही कारणों के लिए काम करता है, बल्कि संकलक, क्रम और JIT, की एक अस्थायी संयोजन के द्वारा काम करने से है जो हो सकता है ढांचे संस्करणों, शारीरिक सीपीयू, मंच, और जैसी चीजों के बीच परिवर्तन x86 बनाम x64।

समझना स्मृति मॉडल एक बहुत ही बहुत जटिल क्षेत्र है, और मैं एक विशेषज्ञ होने का दावा नहीं करते; लेकिन इस क्षेत्र में वास्तविक विशेषज्ञ मुझे आश्वासन देते हैं कि जो व्यवहार आप देख रहे हैं उसकी गारंटी नहीं है।

आप जितने चाहें के रूप में कई कार्य करने वाले उदाहरणों पोस्ट कर सकते हैं, लेकिन दुर्भाग्य से ज्यादा अन्य की तुलना में "यह आम तौर पर काम करता है" साबित नहीं होता कि। यह निश्चित रूप से साबित नहीं करता है कि यह काम करने के लिए की गारंटी है। यह केवल विवाद के लिए एक ही काउंटर उदाहरण लेगा, लेकिन इसे ढूंढना समस्या है ...

नहीं, मेरे पास हाथ नहीं है।


repeatable जवाबी उदाहरण के साथ अद्यतन:

using System.Threading; 
using System; 
static class BackgroundTaskDemo 
{ 
    // make this volatile to fix it 
    private static bool stopping = false; 

    static void Main() 
    { 
     new Thread(DoWork).Start(); 
     Thread.Sleep(5000); 
     stopping = true; 


     Console.WriteLine("Main exit"); 
     Console.ReadLine(); 
    } 

    static void DoWork() 
    { 
     int i = 0; 
     while (!stopping) 
     { 
      i++; 
     } 

     Console.WriteLine("DoWork exit " + i); 
    } 
} 

आउटपुट:

Main exit 

लेकिन अभी भी पूर्ण सीपीयू पर, चल रहा है; ध्यान दें कि stopping को इस बिंदु से true पर सेट किया गया है। ReadLine इतना है कि प्रक्रिया समाप्त नहीं होती है। ऑप्टिमाइज़ेशन लूप के अंदर कोड के आकार पर निर्भर प्रतीत होता है (इसलिए i++)। यह केवल स्पष्ट रूप से "रिलीज" मोड में काम करता है।volatile जोड़ें और यह सब ठीक काम करता है।

+0

अच्छा जवाब। मुझे आश्चर्य है - क्या यह भविष्य के हार्डवेयर, या वर्तमान में हार्डवेयर पर गारंटी नहीं है? भविष्य में हार्डवेयर पर काम नहीं कर सकता है कि किसी चीज के बारे में चिंता करने के लिए मुझे मूर्खतापूर्ण लगता है। –

+0

स्पष्टीकरण के लिए - क्या संकलक, रनटाइम और जेआईटी का असली दुनिया संयोजन है जिसे इस कोड के साथ तोड़ने के लिए दिखाया जा सकता है? –

+0

हां! की तैनाती; इसे रिलीज मोड आदि में चलाएं –

2

FWIW:

  • मैं एमएस सी ++ संकलक (अप्रबंधित कोड) से इस संकलक अनुकूलन देखा है।
  • मैं (जबकि डिबगिंग संकलक अनुकूलन स्वचालित रूप से अक्षम हैं) पता नहीं है कि क्या यह सी # में क्या होता है
  • जबकि डिबगिंग यह नहीं होगा
  • यहां तक ​​कि अगर है कि अनुकूलन अब ऐसा नहीं होता है, तो आप उस दांव लगा रहे हैं वे जेआईटी कंपाइलर के भविष्य के संस्करणों में उस अनुकूलन को कभी भी पेश नहीं करेंगे।
4

इस उदाहरण के रूप में टिप्पणी प्रदर्शित करने के लिए है कि नियंत्रित चर ('stopLooping') कैश किया गया है देशी x86 कोड भी शामिल है।

इसे 'ठीक करने "के लिए अस्थिर करने के लिए' स्टॉप लूपिंग 'बदलें।

यह विजुअल स्टूडियो 2008 के साथ रिलीज बिल्ड के रूप में बनाया गया था और डिबगिंग के बिना चलाया गया था।

using System; 

using System.Threading; 

/* A simple console application which demonstrates the need for 
the volatile keyword and shows the native x86 (JITed) code.*/ 

static class LoopForeverIfWeLoopOnce 
{ 

    private static bool stopLooping = false; 

    static void Main() 
    { 
     new Thread(Loop).Start(); 
     Thread.Sleep(1000); 
     stopLooping = true; 
     Console.Write("Main() is waiting for Enter to be pressed..."); 
     Console.ReadLine(); 
     Console.WriteLine("Main() is returning."); 
    } 

    static void Loop() 
    { 
     /* 
     * Stack frame setup (Native x86 code): 
     * 00000000 push  ebp 
     * 00000001 mov   ebp,esp 
     * 00000003 push  edi 
     * 00000004 push  esi 
     */ 

     int i = 0; 
     /* 
     * Initialize 'i' to zero ('i' is in register edi) 
     * 00000005 xor   edi,edi 
     */ 

     while (!stopLooping) 
      /* 
      * Load 'stopLooping' into eax, test and skip loop if != 0 
      * 00000007 movzx  eax,byte ptr ds:[001E2FE0h] 
      * 0000000e test  eax,eax 
      * 00000010 jne   00000017 
      */ 
     { 
      i++; 
      /* 
      * Increment 'i' 
      * 00000012 inc   edi 
      */ 

      /* 
      * Test the cached value of 'stopped' still in 
      * register eax and do it again if it's still 
      * zero (false), which it is if we get here: 
      * 00000013 test  eax,eax 
      * 00000015 je   00000012 
      */ 
     } 

     Console.WriteLine("i={0}", i); 
    } 
} 
+0

सफलतापूर्वक पुन: उत्पादित। X86 मशीन रनटाइम, रिलीज मोड, कोई डिबगिंग का उपयोग नहीं करना चाहिए। एक x64 आर्किटेक्चर के साथ यह अनुकूलित नहीं है। – TheSoftwareJedi

संबंधित मुद्दे

 संबंधित मुद्दे