2013-02-10 19 views
22

एमएसडीएन पत्रिका में article पढ़ने के परिचय की धारणा पर चर्चा करता है और एक कोड नमूना देता है जिसे इसके द्वारा तोड़ा जा सकता है।सी # में परिचय पढ़ें - इसके खिलाफ कैसे रक्षा करें?

public class ReadIntro { 
    private Object _obj = new Object(); 
    void PrintObj() { 
    Object obj = _obj; 
    if (obj != null) { 
     Console.WriteLine(obj.ToString()); // May throw a NullReferenceException 
    } 
    } 
    void Uninitialize() { 
    _obj = null; 
    } 
} 

सूचना इस टिप्पणी "मई एक NullReferenceException फेंक" - मैं कभी नहीं पता था कि यह संभव हो गया था।

तो मेरा सवाल है: मैं पढ़ने के परिचय के खिलाफ कैसे रक्षा कर सकता हूं?

संकलक वास्तव में एक स्पष्टीकरण के लिए आभारी होंगे जब संकलक पढ़ने को पेश करने का फैसला करता है, क्योंकि लेख में इसे शामिल नहीं किया गया है।

+5

एक जेआईटी कंपाइलर कोड सेमेन्टिक्स बदल रहा है, मुझे चिंता करता है। शायद यह .NET 4.5 से पहले एक जेआईटी बग था। – leppie

+1

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

+0

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

उत्तर

17

मुझे इस जटिल प्रश्न को इसे तोड़कर स्पष्ट करने की कोशिश करें।

"पढ़ना परिचय" क्या है?

public static Foo foo; // I can be changed on another thread! 
void DoBar() { 
    Foo fooLocal = foo; 
    if (fooLocal != null) fooLocal.Bar(); 
} 

स्थानीय चर को नष्ट करने से अनुकूलित है:

"प्रस्तावना पढ़ें" एक अनुकूलन जिससे कोड है। संकलक का कारण बन सकता है यदि केवल एक धागा है तो foo और fooLocal समान बात है। कंपाइलर को किसी भी अनुकूलन को स्पष्ट रूप से अनुमति देने की अनुमति दी जाती है जो एक थ्रेड पर अदृश्य होगा, भले ही यह एक बहुप्रचारित परिदृश्य में दिखाई दे। इसलिए संकलक को इसे फिर से लिखने की अनुमति है:

void DoBar() { 
    if (foo != null) foo.Bar(); 
} 

और अब दौड़ की स्थिति है। यदि foo चेक के बाद गैर-शून्य से शून्य तक बदल जाता है तो यह संभव है कि foo दूसरी बार पढ़ा जाए, और दूसरी बार यह शून्य हो सकता है, जो तब दुर्घटनाग्रस्त हो जाएगा। क्रैश डंप का निदान करने वाले व्यक्ति के परिप्रेक्ष्य से यह पूरी तरह से रहस्यमय होगा।

क्या यह वास्तव में हो सकता है?

लेख आपको बाहर बुलाया से संबंद्ध हैं:

ध्यान दें कि आप NullReferenceException 86-x64 पर .NET फ्रेमवर्क 4.5 में इस कोड नमूने का उपयोग कर पुन: पेश करने में सक्षम नहीं होगा। परिचय .NET Framework 4.5 में पुन: पेश करना बहुत मुश्किल है, लेकिन फिर भी यह कुछ विशेष परिस्थितियों में होता है।

x86/x64 चिप्स के पास "मजबूत" मेमोरी मॉडल है और इस क्षेत्र में जिट कंपाइलर आक्रामक नहीं हैं; वे इस अनुकूलन नहीं करेंगे।

यदि आप एक कमजोर मेमोरी मॉडल प्रोसेसर पर एआरएम चिप की तरह अपना कोड चला रहे हैं, तो सभी दांव बंद हैं।

जब आप कहते हैं कि "कंपाइलर" कौन सा कंपाइलर आपका मतलब है?

मेरा मतलब जिट कंपाइलर है। सी # कंपाइलर इस तरीके से कभी भी पढ़ता नहीं है। (इसकी अनुमति है, लेकिन व्यवहार में यह कभी नहीं करता है।)

क्या स्मृति बाधाओं के बिना धागे के बीच स्मृति साझा करने का बुरा अभ्यास नहीं है?

हां। मेमोरी बाधा पेश करने के लिए यहां कुछ किया जाना चाहिए क्योंकि foo का मान प्रोसेसर कैश में पहले से ही एक पुराना कैश मान हो सकता है। मेमोरी बाधा शुरू करने के लिए मेरी प्राथमिकता लॉक का उपयोग करना है। आप क्षेत्र volatile भी बना सकते हैं, या VolatileRead का उपयोग कर सकते हैं, या Interlocked विधियों में से एक का उपयोग कर सकते हैं। उनमें से सभी एक स्मृति बाधा पेश करते हैं। (volatile केवल "आधा बाड़" FYI प्रस्तुत करता है।)

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

क्या इस पैटर्न के लिए अन्य खतरे हैं?

ज़रूर! मान लीजिए कि कोई पढ़ाई शुरू नहीं है। आपके पास अभी भी दौड़ की स्थिति है। क्या होगा यदि कोई अन्य थ्रेड foo चेक के बाद शून्य पर सेट करता है और यह भी वैश्विक स्थिति को संशोधित करता है कि Bar का उपभोग करने जा रहा है? अब आप दो धागे, जिनमें से एक का मानना ​​है कि foo रिक्त नहीं है और वैश्विक राज्य Bar के लिए एक कॉल के लिए ठीक है, और एक अन्य धागा जो विपरीत का मानना ​​है, और आप Bar चला रहे हैं। यह आपदा के लिए एक नुस्खा है।

तो यहां सबसे अच्छा अभ्यास क्या है?

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

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

तीसरा, अगर आप धागे भर में स्मृति साझा करने के लिए जा रहे हैं तो हर एक विधि आपको लगता है कि फोन शामिल है कि साझा स्मृति या तो दौड़ की स्थिति का सामना करने में मजबूत होना चाहिए, या दौड़ सफाया किया जाना चाहिए। यह सहन करने का भारी बोझ है, और यही कारण है कि आपको पहले स्थान पर नहीं जाना चाहिए।

मेरा मुद्दा यह है: पढ़ना परिचय डरावना है लेकिन स्पष्ट रूप से वे आपकी चिंता का कम से कम हैं यदि आप कोड लिख रहे हैं जो थ्रेड में स्मृति को साझा करता है। पहले के बारे में चिंता करने के लिए एक हजार और एक और चीजें हैं।

+0

तो, आज, यह केवल एआरएम पर हो सकता है? – leppie

+0

इस महान उत्तर के लिए धन्यवाद! मैं अभी भी तर्क दूंगा कि ऐसी परिस्थितियां हैं जहां पढ़ना परिचय एकमात्र चिंता है। उदाहरण के लिए देखें सहकारी रोकथाम के स्टीफन टब के [कार्यान्वयन] (http://blogs.msdn.com/b/pfxteam/archive/2013/01/13/cooperatively-pausing-async-methods.aspx) में 'WaitWhilePausedAsync' विधि देखें । – Gebb

+1

@Gebb: आपका बिंदु अच्छी तरह से लिया गया है। यह दिखाता है कि स्टीफन टब और जो डफी जैसे लोगों को थ्रेडिंग प्राइमेटिव्स के उत्पादन को छोड़ना एक अच्छा कारण है! अपने खुद के primitives लिखने की कोशिश मत करो; 'Lazy ' या 'कार्य ' जैसे प्रकारों का उपयोग करके अमूर्तता का स्तर बढ़ाएं जो इस सामान को समझने वाले लोगों द्वारा लिखे गए थे। –

7

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

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

Object temp = Interlocked.CompareExchange(ref _obj, null, null); 
if(temp != null) 
{ 
    Console.WriteLine(temp.ToString()); 
} 
+0

बस इसे सीधे प्राप्त करने के लिए: यहां 'तुलना एक्सचेंज' का उपयोग करने का एकमात्र कारण है पढ़ने के आसपास मेमोरी बाड़ कंपाइलर को अपने शेंगेनिंग करने से रोकने के लिए '_obj', है ना? –

+0

सही। आदर्श रूप में आप Interlocked.VolatileRead का उपयोग करेंगे, लेकिन इसमें एक सामान्य संस्करण नहीं है, इसलिए तुलना एक्सचेंज बेहतर समाधान है। यह _obj को अस्थिर घोषित करने से भी बेहतर समाधान है, क्योंकि आपको प्रदर्शन कारणों से अस्थिरता से बचना चाहिए जब तक कि आपको हर जगह इसे "सुरक्षित" रखने की आवश्यकता न हो, जो दुर्लभ है .. – Gjeltema

1

मैं केवल लेख स्किम्ड:

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

+0

मेरा पहला विचार यह भी था कि निश्चित रूप से 'निजी अस्थिर वस्तु' _obj' यह असफल नहीं हो सका। लेकिन शायद मेरा अंतर्ज्ञान गलत है। –

+0

यदि आप इसके बारे में चिंतित हैं, तो इस प्रश्न को देखें: http://stackoverflow.com/questions/394898/double-checked-locking-in-net – erikkallen

+0

@erikkallen: स्टीफन टब आपकी परिकल्पना की पुष्टि करता है। कम से कम मुझे इस [वार्तालाप] (http://blogs.msdn.com/b/pfxteam/archive/2013/01/13/cooperatively-pausing-async-methods.aspx) से इस छाप मिल गई। – Gebb