2012-05-14 19 views
5

ive दिया अपने आप पूछ: "मैं क्यों केवल एक बयान के लॉक का उपयोग करना चाहिए" ...केवल 1 ऑपरेशन लॉकिंग?

(IMHO - अगर इसकी 1 ऑपरेशन केवल एक काम की तरह - तो कोई समस्या नहीं होना चाहिए ..)?

तो मैं इस देखा:

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

class ThreadUnsafe 
{ 
    static int _x; 
    static void Increment() { _x++; } 
    static void Assign() { _x = 123; } 
} 

क्या आप मुझे बता क्यों यह सुरक्षित थ्रेड नहीं है कर सकते हैं: निम्नलिखित वर्ग, न बढ़ते क्रम में है और न ही असाइन विधि में धागे की सुरक्षित है? आईव मेरे सिर में कई स्क्रिप्ट चला रहा है और कोई समस्या नहीं मिल सका ...

+0

"एक ऑपरेशन" आमतौर पर "कुछ आपरेशनों" है। अपने कोड की असेंबली देखने के लिए डिबगिंग करते समय डिस्सेप्टर्स को देखें और जांचें कि यह वास्तव में एक ऑपरेशन है या नहीं। यदि ऐसा है - आपको स्पष्ट रूप से लॉक करने की आवश्यकता नहीं है। हालांकि मुझे नहीं लगता कि आपको कभी भी एक ऑपरेशन को लॉक करने की आवश्यकता क्यों होगी, तार्किक रूप से। – SimpleVar

+1

'वृद्धि' असुरक्षित (3 ऑपरेशंस) है। 'असाइन करें' असुरक्षित भी है (1 परमाणु ऑपरेशन लेकिन आपको कैश के माध्यम से मेमोरी सिंक्रनाइज़ेशन का ख्याल रखना होगा)। –

+1

@Soohjun वृद्धि एक परमाणु आपरेशन (आईएनसी DWORD ptr [ईबीपी-3CH]) है – SimpleVar

उत्तर

4

यहां एक उदाहरण है कि आपका उदाहरण थ्रेड-सुरक्षित क्यों नहीं है। प्रारंभ में, _x = 0। मान लीजिए कि आप समानांतर में Increment और Assign चलाते हैं। यदि विधियां थ्रेड-सुरक्षित थीं, तो परिणाम 100 (यदि आवंटन से पहले वृद्धि को निष्पादित किया गया हो) या 101 (यदि आवंटन के बाद वृद्धि को निष्पादित किया गया हो) होना चाहिए।

(संपादित करें: ध्यान दें प्रत्येक थ्रेड यह खुद काम कर ढेर है है!)

Thread 1 (executing Increment) Thread 2 (executing Assign 100) 
----------------------------------------------------------------- 
read _x onto stack  (= 0) 
            put 100 on top of stack 
            write top of stack to _x (= 100) 
increment top of stack (= 1) 
write top of stack to _x (= 1) 

_x अब 1 जो न तो 100 और न ही 101 है।

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


आप एक ताला उपयोग करते हैं, निम्न होता है:

Thread 1 (executing Increment) Thread 2 (executing Assign 100) 
----------------------------------------------------------------- 
lock (success) 
read _x onto stack  (= 0) 
            lock (lock already taken; 
            |  wait until Thead 1's lock is released) 
increment top of stack (= 1) | 
write top of stack to _x (= 1) | 
unlock       | 
            +> (success) 
            put 100 on top of stack 
            write top of stack to _x (= 100) 
            unlock 

परिणाम अब 100 है। असल में, ताला यह सुनिश्चित करता है कि दो लॉक किए गए ब्लॉक ओवरलैप न हों।

+2

वृद्धि = 1 कैसे हो सकती है? दूसरा धागा सेट 100 .... ??? –

+0

मैं दूसरा @ रॉयनामिर। वृद्धि के परिणामस्वरूप 101 हो जाएगा, जहां 1 होने की उम्मीद है - जो मूल रूप से समस्या होगी। – SimpleVar

+0

@ रॉयनामिर: मैंने इसे और स्पष्ट करने के लिए उदाहरण बढ़ाया है। प्रत्येक थ्रेड में इसका अपना कामकाजी ढेर होता है। – Heinzi

1

आपको प्रोग्रामिंग भाषा की तुलना में भी कम स्तर पर सोचना होगा।

कोई गारंटी नहीं कि

क) प्रोसेसर एक ही बार (परमाणु या गैर परमाणु)

ख) मूल्य एक सीपीयू कोर के कैश में अद्यतन किया जाएगा में नया मान लिखेंगे सब है, लेकिन दूसरे में नहीं (स्मृति बाधाओं की कमी)

शायद आपका सीपीयू (संभवतः) 32-बिट पूर्णांक को पढ़ और लिख सकता है और आपको कोई समस्या नहीं होगी। लेकिन जब आप 64-बिट मान को पढ़ने/लिखने का प्रयास कर रहे हैं तो क्या होता है? एक 128? मान एक अंतःविषय स्थिति में समाप्त हो सकता है जहां दो अलग-अलग धागे एक ही मेमोरी लोकेशन को एक साथ संशोधित कर रहे हैं, और आप या तो मान ए, वैल्यू बी, या इंटरमीडिएट (और बहुत गलत) मान के साथ समाप्त होते हैं जो दोनों का मिश्रण होता है।

और कई अन्य।

+0

असल में सी # गारंटी देता है जो Int32 * को असाइन करता है * परमाणु। –

+0

संदर्भ spec int अद्यतनों के अनुसार परमाणु हैं, हालांकि रजिस्टर कैशिंग की दूरस्थ स्थिति है। –

+0

@Sohohjun लेकिन स्मृति बाधाओं के माध्यम से जरूरी नहीं, मुझे लगता है? इससे कैश समेकन की समस्या निकलती है। –

4

वेतन वृद्धि आपरेशन इस MSIL ...

.method private hidebysig static void Increment() cil managed 
{ 
    // Code size  14 (0xe) 
    .maxstack 8 
    IL_0000: nop 
    IL_0001: ldsfld  int32 ThreadUnsafe::_x 
    IL_0006: ldc.i4.1 
    IL_0007: add 
    IL_0008: stsfld  int32 ThreadUnsafe::_x 
    IL_000d: ret 
} // end of method ThreadUnsafe::Increment 

तो आप देख सकते हैं, यहां तक ​​कि MSIL स्तर पर पैदा करता है, वेतन वृद्धि परमाणु नहीं है। जेआईटी कंपाइलर मशीन स्तर पर परमाणु वृद्धि में इसे वापस लाने के लिए कुछ चालाक कर सकता है, लेकिन हम निश्चित रूप से उस पर निर्भर नहीं हो सकते हैं। कल्पना करें कि एक ही एक्स को उनके "लोड" और "स्टोर" ऑपरेशंस ओवरलैप किए गए एक्स के साथ बढ़ाना है - आप देख सकते हैं कि X + 2.

लॉक के अंदर अपनी वृद्धि को लपेटना इसका मतलब है कि वे ओवरलैप नहीं कर सकते हैं।

0

ताला लगा एक बड़ा गंदा अधीन है, आप आमतौर पर पता लगाना क्या हुड के नीचे चला जाता है (जो कोर कैश अवैध हो जाता है जब) एक बहुत ही मुश्किल समय होगा। यही कारण है कि कुशल समानांतर कोड लिखना एक समस्या है। दूसरों ने एक असाइनमेंट के साथ कुछ संभावित मुद्दों को भी इंगित किया है (और स्पष्ट रूप से एक चर को बढ़ाने के साथ)। बस volatile कीवर्ड के साथ सभी मुद्दों को देखें: https://www.google.com/search?q=.net+volatile+concurrency&ie=utf-8&oe=utf-8&aq=t&rls=org.mozilla:en-US:official&client=firefox-a

इसलिए, यदि आप समानांतर में काम करने होंगे, ताला लगा एक बहुत द्वारा शुरू करते हैं, यहां तक ​​कि कार्यों पर आपको नहीं लगता कि ताले की आवश्यकता है। जब आप प्रदर्शन समस्याओं को देख रहे हों तो केवल अपनी लॉकिंग को अनुकूलित करें।

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