2009-11-24 7 views
49

कुछ भाषाएं volatile संशोधक प्रदान करती हैं जो एक चर का बैक अप लेने वाली स्मृति को पढ़ने से पहले "मेमोरी बाधा पढ़ें" के रूप में वर्णित है।मैं मेमोरी बाधाओं और अस्थिरता को कैसे समझूं

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

तो, अस्थिर वास्तव में गारंटी देता है कि एक अद्यतित मूल्य पढ़ा जाता है या बस (gasp!) कि पढ़े गए मान कम से कम अद्यतित हैं जितना कि बाधा से पहले पढ़ता है? या कुछ और व्याख्या? इस उत्तर के व्यावहारिक प्रभाव क्या हैं?

उत्तर

93

पढ़ी बाधाएं और बाधाएं लिखना; बाधाओं को प्राप्त करें और बाधाओं को छोड़ दें। और अधिक (io बनाम स्मृति, आदि)।

बाधाएं मूल्यों के "नवीनतम" मूल्य या "ताजगी" को नियंत्रित करने के लिए नहीं हैं। वे मेमोरी एक्सेस के रिश्तेदार क्रम को नियंत्रित करने के लिए वहां हैं।

बाधाओं को लिखने के क्रम को नियंत्रित करें लिखें। चूंकि स्मृति में लिखना धीमा होता है (सीपीयू की गति की तुलना में), आम तौर पर एक लेखन-अनुरोध कतार होती है जहां लिखने से पहले पोस्ट किया जाता है। यद्यपि वे क्रम में कतारबद्ध हैं, जबकि कतार के अंदर लिखने को फिर से व्यवस्थित किया जा सकता है। (तो हो सकता है कि 'कतार' सबसे अच्छा नाम न हो ...) जब तक आप रीडरिंग को रोकने के लिए लिखने की बाधाओं का उपयोग न करें।

पढ़ें बाधाओं को पढ़ने के क्रम को नियंत्रित करें। सट्टा निष्पादन की वजह से (सीपीयू आगे दिखता है और स्मृति से लोड होता है) और लिखने वाले बफर के अस्तित्व के कारण (सीपीयू मेमोरी के बजाय लिखने वाले बफर से एक मान पढ़ेगा यदि यह वहां है - यानी सीपीयू सोचता है कि यह सिर्फ एक्स लिखा है = 5, फिर इसे वापस क्यों पढ़ें, बस यह देखें कि यह अभी भी 5 लिखने वाले बफर में प्रतीक्षा कर रहा है) पढ़ता क्रम से बाहर हो सकता है।

यह संकुलक है कि संकलक जेनरेट कोड के आदेश के संबंध में क्या करने का प्रयास करता है। यानी सी ++ में 'अस्थिर' यहां मदद नहीं करेगा, क्योंकि यह केवल "मेमोरी" से मूल्य को फिर से पढ़ने के लिए आउटपुट कोड को संकलक को बताता है, यह सीपीयू को यह नहीं बताता है कि इसे कैसे/कहां से पढ़ा जाए (यानी "स्मृति" सीपीयू स्तर पर कई चीजें हैं)।

तो पढ़ना/लिखना बाधाएं पढ़ने/लिखने वाली कतारों में पुनरावृत्ति को रोकने के लिए ब्लॉक डालती हैं (पठन आमतौर पर कतार का इतना अधिक नहीं होता है, लेकिन पुनरावृत्ति प्रभाव समान होते हैं)।

किस प्रकार के ब्लॉक? - ब्लॉक प्राप्त करें और/या रिलीज करें।

मोल - जैसे-पढ़ने के लिए अधिग्रहण (एक्स) पढ़ने कतार में एक्स के पढ़ने जोड़ सकते हैं और कतार (वास्तव में कतार फ्लश नहीं फ्लश, लेकिन यह कहते हुए इस पढ़ने से पहले कुछ भी पुन: व्यवस्थित नहीं है एक मार्कर जोड़ देगा , जैसे कि कतार फ्लश किया गया था)। तो बाद में (कोड ऑर्डर में) पढ़ा जा सकता है, लेकिन एक्स के पढ़ने से पहले नहीं।

रिलीज - उदाहरण के लिए लिखना-रिलीज (एक्स, 5) पहले कतार फ्लश करेगा (या मार्कर), फिर लेखन-कतार में लेखन-अनुरोध जोड़ें। तो पहले लिखते हैं x = 5 के बाद होने के लिए पुन: व्यवस्थित नहीं होंगे, लेकिन ध्यान दें कि बाद में लिखने को x = 5.

ध्यान दें कि मैंने रिलीज के साथ पढ़ने और लिखने के साथ पढ़ा है क्योंकि यह सामान्य है, लेकिन विभिन्न संयोजन संभव हैं।

प्राप्त करें और रिलीज को 'आधे बाधाओं' या 'आधे बाड़' माना जाता है क्योंकि वे केवल एक ही रास्ता जाने से पीछे हटना बंद कर देते हैं।

एक पूर्ण बाधा (या पूर्ण बाड़) एक अधिग्रहण और एक रिलीज दोनों लागू होता है - यानी कोई पुनरावृत्ति नहीं।

आमतौर पर लॉकफ्री प्रोग्रामिंग, या सी # या जावा 'अस्थिर' के लिए, जो आप चाहते हैं/चाहिए पढ़ना और लिखना-रिलीज करना है।

यानी

void threadA() 
{ 
    foo->x = 10; 
    foo->y = 11; 
    foo->z = 12; 
    write_release(foo->ready, true); 
    bar = 13; 
} 
void threadB() 
{ 
    w = some_global; 
    ready = read_acquire(foo->ready); 
    if (ready) 
    { 
     q = w * foo->x * foo->y * foo->z; 
    } 
    else 
     calculate_pi(); 
} 

तो, सब से पहले, यह एक बुरा तरीका धागे कार्यक्रम है। ताले सुरक्षित होंगे। लेकिन बस बाधाओं को चित्रित करने के लिए ...

थ्रेडए() के बाद foo लिखने के बाद, इसे foo-> तैयार तैयार लिखने की जरूरत है, वास्तव में आखिरी है, अन्यथा अन्य धागे foo-> जल्दी तैयार हो सकते हैं और गलत मान प्राप्त कर सकते हैं x/y/z। तो हम foo-> तैयार पर write_release का उपयोग करते हैं, जैसा ऊपर बताया गया है, प्रभावी रूप से लिखने वाली कतार 'फ्लैश' करता है (एक्स, वाई, जेड सुनिश्चित कर रहा है) फिर कतार में तैयार = सही अनुरोध जोड़ता है। और फिर बार = 13 अनुरोध जोड़ता है। ध्यान दें कि चूंकि हमने अभी रिलीज बाधा का उपयोग किया है (पूर्ण नहीं) बार = 13 तैयार होने से पहले लिखा जा सकता है। लेकिन हमें परवाह नहीं है! यानी हम मानते हैं कि बार साझा डेटा नहीं बदल रहा है।

अब थ्रेडबी() को यह जानने की जरूरत है कि जब हम 'तैयार' कहते हैं तो हम वास्तव में तैयार हैं। तो हम read_acquire(foo->ready) करते हैं। यह पढ़ा पढ़ा कतार में जोड़ा जाता है, फिर कतार फिसल जाती है। ध्यान दें कि w = some_global अभी भी कतार में हो सकता है। तो foo-> तैयार some_global से पहले पढ़ा जा सकता है। लेकिन फिर, हमें परवाह नहीं है, क्योंकि यह महत्वपूर्ण डेटा का हिस्सा नहीं है कि हम इस बारे में बहुत सावधान हैं। हम किस बारे में परवाह करते हैं foo-> x/y/z है। इसलिए उन्हें फ्लश/मार्कर प्राप्त करने के बाद पढ़ने की कतार में जोड़ा जाता है, यह गारंटी देता है कि वे केवल foo-> तैयार पढ़ने के बाद पढ़े जाते हैं।

ध्यान दें, यह आमतौर पर एक म्यूटेक्स/क्रिटिकलसेक्शन/आदि को लॉक करने और अनलॉक करने के लिए उपयोग की जाने वाली सटीक समान बाधाएं होती है। (यानी लॉक() पर अधिग्रहण, अनलॉक() पर रिलीज)।

तो,

  • मैं बहुत यकीन है कि यह (यानी अधिग्रहण/रिलीज) वास्तव में एमएस डॉक्स क्या कहते हैं पढ़ने के लिए होता है (और सी # में 'अस्थिर' चर का/लिखते एमएस सी के लिए वैकल्पिक रूप से ++ हूँ, लेकिन यह गैर मानक है)। सहित http://msdn.microsoft.com/en-us/library/aa645755(VS.71).aspx देखें "एक अस्थिर पढ़ा है" अर्थ विज्ञान के अधिग्रहण ", वह यह है कि यह स्मृति के सभी संदर्भ है कि यह बाद हो से पहले होने की गारंटी है ..."

  • मुझे लगता है कि जावा में एक ही है, हालांकि मैं परिचित नहीं हूँ। मुझे संदेह है कि यह बिल्कुल वही है, क्योंकि आपको आमतौर पर पढ़ने-प्राप्त/लिखने-रिलीज की तुलना में अधिक गारंटी की आवश्यकता नहीं होती है।

  • आपके प्रश्न में जब आप सोचते हैं कि यह वास्तव में रिश्तेदार आदेश के बारे में है तो आप सही रास्ते पर थे - आप केवल ऑर्डरिंग पीछे थे (यानी "जो मान पढ़े जाते हैं वे कम से कम अद्यतित होते हैं बाधा से पहले पढ़ता है? "- नहीं, अवरोधक महत्वहीन होने से पहले पढ़ता है, इसके बाद बाधा के बाद पढ़ा जाता है जो लिखने के लिए विपरीत होता है)।

  • और कृपया ध्यान दें, जैसा कि बताया गया है, रीडॉर्डिंग दोनों पढ़ने और लिखने पर होता है, इसलिए केवल एक थ्रेड पर बाधा का उपयोग करना और अन्य काम नहीं करेगा। यानी एक लेखन-रिलीज पढ़ने-अधिग्रहण के बिना पर्याप्त नहीं है। यानी यदि आप इसे सही क्रम में लिखते हैं, तो भी गलत आदेश में पढ़ा जा सकता है यदि आपने लिखने की बाधाओं के साथ पढ़ने के लिए पढ़ने की बाधाओं का उपयोग नहीं किया है।

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

+1

क्या इससे कोई फर्क नहीं पड़ता कि write_release और read_acquire संदर्भ एक ही तैयार चर है? या आप दोनों के लिए अलग डमी चर का उपयोग कर सकते हैं? लगता है कि मूल्य का कोई उद्देश्य नहीं है। –

+2

सिंक्रनाइज़ करने की कोशिश कर रहे थ्रेड के लिए समान चर का उपयोग करना आवश्यक है। जैसे कि आपको उसी म्यूटेक्स का उपयोग करने या सामान्य थ्रेडिंग में लॉक करने की आवश्यकता होती है। मेरे धागा ए/बी उदाहरण में, हम यह सुनिश्चित करना चाहते हैं कि foo-> x, y, z foo-> तैयार होने से पहले लिखा गया है (अन्यथा कोई वास्तव में तैयार होने से पहले 'तैयार == सत्य' देख सकता है)। पढ़ने की तरफ, आप तैयार होने से पहले x, y, z को नहीं पढ़ना चाहते हैं, इसलिए आपको foo-> पर read_acquire की आवश्यकता है ताकि यह सुनिश्चित किया जा सके कि CPU x, y, z को पुन: व्यवस्थित नहीं करता है ' अगर (foo-> तैयार) '। यदि आपकी बाधाएं विभिन्न डमी चर पर थीं, तो आपके पास सिंक-पॉइंट नहीं होगा। – tony

8

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

इसके विपरीत सीपीयू मेमोरी बाधाओं को सीधे सीपीयू निर्देशों के माध्यम से प्रदान किया जाता है या कुछ असेंबलर निमोनिक्स (जैसे कि lock x86 में उपसर्ग) के साथ निहित किया जाता है और उदाहरण के लिए हार्डवेयर उपकरणों से बात करते समय उपयोग किया जाता है जहां स्मृति-मैप किए गए IO को पढ़ने और लिखने का क्रम रजिस्ट्रार बहु-प्रसंस्करण वातावरण में स्मृति पहुंच को महत्वपूर्ण या सिंक्रनाइज़ करना महत्वपूर्ण है।

अपने प्रश्न का उत्तर देने के लिए - नहीं, मेमोरी बाधा "नवीनतम" मान की गारंटी नहीं देती है, लेकिन मेमोरी एक्सेस ऑपरेशंस के ऑर्डर की गारंटी देता है। उदाहरण के लिए यह lock-free प्रोग्रामिंग में महत्वपूर्ण है।

Here सीपीयू मेमोरी बाधाओं पर प्राइमरों में से एक है।

+0

मुझे पता है कि सी और सी ++ के कई कार्यान्वयन में यह मामला है। मेरा प्रश्न जावा और .NET जैसे वर्चुअल मशीन प्लेटफ़ॉर्म के लिए सबसे प्रासंगिक है। –

+0

वीएम-आधारित भाषाओं जैसे जावा और सी # के लिए आपको यह पता लगाना होगा कि उनका "मेमोरी मॉडल" क्या है। –

+0

ध्यान दें कि 'अस्थिर' पर्याप्त नहीं है, आपको सिग्नल हैंडलर में मानक-अनुरूप उपयोग के लिए 'अस्थिर sig_atomic_t' का उपयोग करना होगा। – Jed

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