यह कोड रिलीज़ मोड में लटका हुआ है, लेकिन डीबग मोड में ठीक काम करता है


110

मैं इस पर आया था और डिबग और रिलीज़ मोड में इस व्यवहार का कारण जानना चाहता हूं।

public static void Main(string[] args)
{            
   bool isComplete = false;

   var t = new Thread(() =>
   {
       int i = 0;

        while (!isComplete) i += 0;
   });

   t.Start();

   Thread.Sleep(500);
   isComplete = true;
   t.Join();
   Console.WriteLine("complete!");
}

25
व्यवहार में वास्तव में क्या अंतर है?
मोंग झू

4
यदि यह जावा था तो मैं मानूंगा कि संकलक को 'संकलन' चर में अपडेट नहीं दिखता है। परिवर्तनशील घोषणा में 'वाष्पशील' जोड़ने से वह ठीक हो जाएगा (और इसे एक स्थिर क्षेत्र बना देगा)।
सेबस्टियन


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

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

जवाबों:


149

मुझे लगता है कि आशावादी को isCompleteचर पर 'अस्थिर' कीवर्ड की कमी से मूर्ख बनाया गया है ।

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

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

public static void Main(string[] args)
{
    TheHelper hlp = new TheHelper();

    var t = new Thread(hlp.Body);

    t.Start();

    Thread.Sleep(500);
    hlp.isComplete = true;
    t.Join();
    Console.WriteLine("complete!");
}

private class TheHelper
{
    public bool isComplete = false;

    public void Body()
    {
        int i = 0;

        while (!isComplete) i += 0;
    }
}

मैं अब कल्पना कर सकता हूं कि प्रसंस्करण TheHelperवर्ग जब एक मल्टीथ्रेडेड वातावरण में जेआईटी कंपाइलर / ऑप्टिमाइज़र , वास्तव में विधि की शुरुआत में कुछ रजिस्टर या स्टैक फ्रेम में मूल्य को कैश कर सकता है , और विधि समाप्त होने तक इसे कभी भी ताज़ा नहीं करता है। ऐसा इसलिए है क्योंकि कोई भी गारंटी नहीं है कि "= सच" निष्पादित होने से पहले धागा और विधि समाप्त नहीं होगी, इसलिए यदि कोई गारंटी नहीं है, तो इसे कैश क्यों न करें और इसे पढ़ने के बजाय एक बार ढेर ऑब्जेक्ट को पढ़ने के प्रदर्शन को बढ़ावा दें। यात्रा।falseBody()

यही कारण है कि कीवर्ड volatileमौजूद है।

इस सहायक स्तरीय होने के लिए सही एक छोटे से थोड़ा बेहतर थोड़ा 1) मल्टी-थ्रेडेड वातावरण में, यह होना चाहिए:

    public volatile bool isComplete = false;

लेकिन, ज़ाहिर है, चूंकि यह ऑटोगेन्जेनेटेड कोड है, इसलिए आप इसे नहीं जोड़ सकते। एक बेहतर तरीका यह है कि कुछ lock()एस को पढ़ने और लिखने के लिए जोड़ा जाए isCompletedया कुछ अन्य रेडी-टू-यूज़ सिंक्रनाइजेशन या थ्रेडिंग / टास्किंग उपयोगिताओं का उपयोग करने के बजाय इसे नंगे-धातु करने की कोशिश की जाए (जो कि यह नंगे-धातु नहीं होगा , चूंकि यह जीसी, जेआईटी और (..) के साथ सीएलआर पर सी # है।

डिबग मोड में अंतर शायद इसलिए होता है क्योंकि डिबग मोड में कई ऑप्टिमाइज़ेशन को बाहर रखा जाता है, इसलिए आप स्क्रीन पर देखे गए कोड को अच्छी तरह से डिबग कर सकते हैं। इसलिए while (!isComplete)अनुकूलित नहीं है, इसलिए आप वहां एक ब्रेकपॉइंट सेट कर सकते हैं, और इसलिए isCompleteआक्रामक तरीके से रजिस्टर या स्टैक में कैश नहीं किया जाता है और हर लूप पुनरावृत्ति पर हीप से ऑब्जेक्ट से पढ़ा जाता है।

Btw। उस पर बस मेरा अनुमान है। मैंने इसे संकलित करने की कोशिश भी नहीं की।

Btw। यह एक बग नहीं लगता है; यह बहुत अस्पष्ट साइड इफेक्ट की तरह है। इसके अलावा, अगर मैं इसके बारे में सही हूं, तो यह एक भाषा की कमी हो सकती है - सी # को स्थानीय चर पर 'अस्थिर' कीवर्ड को रखने की अनुमति देनी चाहिए जो बंद क्षेत्रों में सदस्य क्षेत्रों पर कब्जा कर लिया गया है और पदोन्नत किया गया है।

1) के बारे में एरिक Lippert से एक टिप्पणी के लिए नीचे देखें volatileऔर / या इस बहुत ही दिलचस्प है कि कोड पर निर्भर सुनिश्चित करने में शामिल जटिलता के स्तर दिखा लेख volatileहै सुरक्षित ..uh, अच्छा ..uh, चलो ठीक कहते हैं करते हैं।


2
@EricLippert: वाह, बहुत तेजी से पुष्टि करने के लिए बहुत बहुत धन्यवाद! आपको क्या लगता है, क्या कोई मौका है कि भविष्य के किसी संस्करण में हमें volatileकैप्चर-इन-क्लोजर स्थानीय चर पर एक विकल्प मिल सकता है ? मुझे लगता है कि कंपाइलर द्वारा प्रोसेस करना थोड़ा मुश्किल हो सकता है ..
quetzalcoatl

7
@quetzalcoatl: मैं उस सुविधा पर भरोसा नहीं करूंगा जो किसी भी समय जल्द ही जोड़ा जाएगा। यह उस तरह की कोडिंग है जिसे आप हतोत्साहित करना चाहते हैं , और आसान नहीं । और इसके अलावा, सामान को अस्थिर बनाने से हर समस्या का समाधान नहीं होता है। यहां एक उदाहरण है जहां सब कुछ अस्थिर है और कार्यक्रम अभी भी गलत है; क्या आप बग ढूंढ सकते हैं? blog.coverity.com/2014/03/26/reordering-optimifications
Eric Lippert

3
समझ लिया। मैं मल्टीथ्रेडिंग ऑप्टिमाइज़ेशन को समझने की कोशिश करना छोड़ देता हूं ... यह पागल है कि यह कितना जटिल है।
inBetween

10
@Pikoh: फिर से, एक अनुकूलक की तरह सोचें। आपके पास एक चर है जो बढ़ा हुआ है लेकिन कभी पढ़ा नहीं गया। एक चर जो कभी नहीं पढ़ा जाता है उसे पूरी तरह से हटाया जा सकता है।
एरिक लिपर्ट 13-13

4
@EricLippert अब मेरे दिमाग ने एक क्लिक किया। यह धागा बहुत जानकारीपूर्ण रहा है, बहुत बहुत धन्यवाद।
पिकोह

82

Quetzalcoatl का जवाब सही है। इस पर अधिक प्रकाश डालने के लिए:

सी # कंपाइलर और सीएलआर घबराना को बहुत अधिक अनुकूलन करने की अनुमति है जो यह मानते हैं कि वर्तमान थ्रेड एकमात्र थ्रेड चल रहा है। यदि वे अनुकूलन एक ऐसी दुनिया में कार्यक्रम को गलत बनाते हैं जहां वर्तमान धागा केवल चलने वाला धागा नहीं है जो आपकी समस्या है । आपको मल्टीथ्रेडेड प्रोग्राम लिखना आवश्यक है जो कंपाइलर और घबराना बताता है कि आप क्या पागल मल्टीथ्रेडेड सामान कर रहे हैं।

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

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


1
टिप्पणियाँ विस्तारित चर्चा के लिए नहीं हैं; इस वार्तालाप को बातचीत में स्थानांतरित कर दिया गया है ।
मादरा का भूत

14

मैं दौड़ने की प्रक्रिया से जुड़ा हुआ था और पाया (अगर मैंने गलतियाँ नहीं कीं, तो मुझे इस बात का बहुत अभ्यास नहीं है) कि इस Threadविधि का अनुवाद इस प्रकार है:

debug051:02DE04EB loc_2DE04EB:                            
debug051:02DE04EB test    eax, eax
debug051:02DE04ED jz      short loc_2DE04EB
debug051:02DE04EF pop     ebp
debug051:02DE04F0 retn

eax(जिसमें मूल्य होता है isComplete) पहली बार लोड किया गया और कभी ताज़ा नहीं हुआ।


8

वास्तव में एक उत्तर नहीं है, लेकिन इस मुद्दे पर कुछ और प्रकाश डालने के लिए:

समस्या तब लगती है जब iलंबोदर के शरीर के अंदर घोषित किया जाता है और यह केवल असाइनमेंट अभिव्यक्ति में पढ़ा जाता है। अन्यथा, कोड रिलीज़ मोड में अच्छा काम करता है:

  1. i मेमने के शरीर के बाहर घोषित:

    int i = 0; // Declared outside the lambda body
    
    var t = new Thread(() =>
    {
        while (!isComplete) { i += 0; }
    }); // Completes in release mode
  2. i असाइनमेंट एक्सप्रेशन में नहीं पढ़ा गया है:

    var t = new Thread(() =>
    {
        int i = 0;
        while (!isComplete) { i = 0; }
    }); // Completes in release mode
  3. i यह भी कहीं और पढ़ा जाता है:

    var t = new Thread(() =>
    {
        int i = 0;
        while (!isComplete) { Console.WriteLine(i); i += 0; }
    }); // Completes in release mode

मेरी शर्त कुछ संकलक या JIT अनुकूलन है जो iचीजों को गड़बड़ कर रहा है। मुझसे ज्यादा होशियार कोई व्यक्ति शायद इस मुद्दे पर ज्यादा प्रकाश डाल सकेगा।

फिर भी, मैं इसके बारे में बहुत अधिक चिंता नहीं करूंगा, क्योंकि मैं यह देखने में विफल रहता हूं कि समान कोड वास्तव में किसी उद्देश्य की सेवा कैसे करेगा।


1
मेरा उत्तर देखिए, मुझे पूरा यकीन है कि यह 'वाष्पशील' कीवर्ड के बारे में है कि एक स्थानीय चर में जोड़ा जा सकता है (जो वास्तव में बाद में बंद होने वाले सदस्य क्षेत्र में पदोन्नत हो जाता है) ..
quetzalcoatl
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.