मुझे इस जटिल प्रश्न को इसे तोड़कर स्पष्ट करने की कोशिश करें।
"पढ़ना परिचय" क्या है?
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
चला रहे हैं। यह आपदा के लिए एक नुस्खा है।
तो यहां सबसे अच्छा अभ्यास क्या है?
पहले, धागे भर में स्मृति का हिस्सा नहीं है। यह पूरा विचार है कि आपके प्रोग्राम की मुख्य पंक्ति के अंदर नियंत्रण के दो धागे हैं, बस शुरुआत करने के लिए पागल है। यह पहली जगह में कभी नहीं होना चाहिए था।हल्के वजन के रूप में धागे का प्रयोग करें; उन्हें प्रदर्शन करने के लिए एक स्वतंत्र कार्य दें जो कार्यक्रम की मुख्य पंक्ति की स्मृति के साथ बातचीत नहीं करता है, और केवल कम्प्यूटेशनल गहन कार्य को खेत के लिए उपयोग करें।
दूसरा, यदि आप थ्रेड में मेमोरी साझा करने जा रहे हैं तो उस मेमोरी तक पहुंच को क्रमबद्ध करने के लिए ताले का उपयोग करें। ताले सस्ते होते हैं यदि उनका विरोध नहीं किया जाता है, और यदि आपके पास विवाद होता है, तो उस समस्या को ठीक करें। लो-लॉक और नो-लॉक समाधान सही होने के लिए कुख्यात रूप से मुश्किल हैं।
तीसरा, अगर आप धागे भर में स्मृति साझा करने के लिए जा रहे हैं तो हर एक विधि आपको लगता है कि फोन शामिल है कि साझा स्मृति या तो दौड़ की स्थिति का सामना करने में मजबूत होना चाहिए, या दौड़ सफाया किया जाना चाहिए। यह सहन करने का भारी बोझ है, और यही कारण है कि आपको पहले स्थान पर नहीं जाना चाहिए।
मेरा मुद्दा यह है: पढ़ना परिचय डरावना है लेकिन स्पष्ट रूप से वे आपकी चिंता का कम से कम हैं यदि आप कोड लिख रहे हैं जो थ्रेड में स्मृति को साझा करता है। पहले के बारे में चिंता करने के लिए एक हजार और एक और चीजें हैं।
एक जेआईटी कंपाइलर कोड सेमेन्टिक्स बदल रहा है, मुझे चिंता करता है। शायद यह .NET 4.5 से पहले एक जेआईटी बग था। – leppie
हाँ, यह निश्चित रूप से एक जेआईटी बग की तरह दिखता है। असल में, मैं इसे एक बग के रूप में वर्गीकृत करता हूं चाहे कोई फर्क नहीं पड़ता। लेख थोड़ा बेकार है - यह अवधारणा का उल्लेख करता है लेकिन वास्तव में यह नहीं कहता कि यह कैसे होता है और इसके खिलाफ कैसे रक्षा करें। –
पढ़ा गया परिचय भाग स्पष्ट रूप से बहु-थ्रेडिंग का उल्लेख नहीं करता है लेकिन परिचय कहता है "कंपाइलर और हार्डवेयर प्रोग्राम के मेमोरी ऑपरेशंस को उन तरीकों से बदल सकते हैं जो एकल-थ्रेडेड व्यवहार को प्रभावित नहीं करते हैं, लेकिन बहु-थ्रेडेड को प्रभावित कर सकते हैं व्यवहार।"। लॉक का प्रयोग उस मामले में आपके प्रश्न का उत्तर होगा। – rene