2009-02-02 14 views
42

यह सी # के लिए एक विस्तृत प्रश्न है।सी # थ्रेड सुरक्षा प्राप्त/सेट

मान लीजिए मैं एक वस्तु के साथ एक वर्ग मिल गया है, और कहा कि वस्तु एक ताला द्वारा सुरक्षित है:

Object mLock = new Object(); 
MyObject property; 
public MyObject MyProperty { 
    get { 
     return property; 
    } 
    set { 
     property = value; 
    } 
} 

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

क्या निम्न कोड डेटा को ठीक से लॉक करेगा?

Object mLock = new Object(); 
MyObject property; 
public MyObject MyProperty { 
    get { 
     lock (mLock){ 
      return property; 
     } 
    } 
    set { 
     lock (mLock){ 
       property = value; 
     } 
    } 
} 

तक 'ठीक से', मैं क्या मतलब है, अगर मैं

MyProperty.Field1 = 2; 

या जो कुछ भी, क्षेत्र लॉक कर दिया जाएगा कॉल करने के लिए जब मैं अद्यतन करना चाहते हैं? क्या यह सेटिंग 'get' फ़ंक्शन के दायरे के अंदर बराबर ऑपरेटर द्वारा की जाती है, या 'get' फ़ंक्शन (और इसलिए लॉक) पहले खत्म हो जाएगी, और फिर सेटिंग, और फिर 'सेट' कहा जाता है, इस प्रकार बाईपासिंग ताला?

संपादित करें: चूंकि यह स्पष्ट रूप से चाल नहीं करेगा, क्या होगा? मैं की तरह कुछ करने की ज़रूरत है:

Object mLock = new Object(); 
MyObject property; 
public MyObject MyProperty { 
    get { 
     MyObject tmp = null; 
     lock (mLock){ 
      tmp = property.Clone(); 
     } 
     return tmp; 
    } 
    set { 
     lock (mLock){ 
       property = value; 
     } 
    } 
} 

कम या ज्यादा बस सुनिश्चित करें कि मैं केवल एक प्रति के लिए उपयोग किया है जो, जिसका अर्थ है कि अगर मैं के लिए गए थे दो धागे फोन एक ही समय में 'प्राप्त', वे प्रत्येक फ़ील्ड 1 (दाएं?) के समान मूल्य से शुरू करेंगे। क्या ऐसी संपत्ति पर लॉकिंग पढ़ने और लिखने का कोई तरीका है जो समझ में आता है? या क्या मुझे खुद को डेटा के बजाय कार्यों के अनुभागों को लॉक करने के लिए बाध्य होना चाहिए?

बस इतना उदाहरण यह है कि यह उदाहरण समझ में आता है: MyObject एक डिवाइस ड्राइवर है जो अतुल्यकालिक स्थिति देता है। मैं इसे एक धारावाहिक बंदरगाह के माध्यम से आदेश भेजता हूं, और फिर डिवाइस उन आदेशों को अपने स्वयं के मीठे समय में प्रतिसाद देता है। अभी, मेरे पास एक धागा है जो इसे अपनी स्थिति के लिए मतदान करता है ("क्या आप अभी भी वहां हैं? क्या आप कमांड स्वीकार कर सकते हैं?"), एक थ्रेड जो सीरियल पोर्ट पर प्रतिक्रियाओं की प्रतीक्षा करता है ("बस स्टेटस स्ट्रिंग 2 मिला, सबकुछ ठीक है"), और फिर यूआई थ्रेड जो अन्य कमांडों में लेता है ("उपयोगकर्ता चाहता है कि आप यह काम करें।") और ड्राइवर से प्रतिक्रिया पोस्ट करता है ("मैंने अभी यह काम किया है, अब उसके साथ यूआई अपडेट करें")। यही कारण है कि मैं ऑब्जेक्ट के क्षेत्र की बजाय ऑब्जेक्ट को लॉक करना चाहता हूं; यह ताले, ए, और बी की एक बड़ी संख्या होगी, इस वर्ग के हर डिवाइस में एक ही व्यवहार नहीं है, केवल सामान्य व्यवहार है, इसलिए यदि मैं ताले को वैयक्तिकृत करता हूं तो मुझे कई अलग-अलग संवाद कोड करना होगा।

उत्तर

34

नहीं, आपका कोड MyProperty से लौटे ऑब्जेक्ट के सदस्यों तक पहुंच लॉक नहीं करेगा। यह केवल MyProperty ताले लगा देता है।

आपका उदाहरण उपयोग वास्तव में है दो आपरेशन में लुढ़का, मोटे तौर पर इस के बराबर:

// object is locked and then immediately released in the MyProperty getter 
MyObject o = MyProperty; 

// this assignment isn't covered by a lock 
o.Field1 = 2; 

// the MyProperty setter is never even called in this example 

संक्षेप में - अगर दो धागे MyProperty एक साथ का उपयोग, गेटर संक्षेप में जब तक यह रिटर्न दूसरा धागा को अवरुद्ध कर देगा पहले धागे पर ऑब्जेक्ट, लेकिन यह ऑब्जेक्ट को दूसरे थ्रेड पर भी वापस कर देगा। दोनों धागे के पास ऑब्जेक्ट तक पूर्ण, अनलॉक पहुंच होगी। सवाल में अधिक जानकारी के जवाब में

संपादित

मैं अभी भी नहीं 100% कुछ आप क्या हासिल करने की कोशिश कर रहे हैं, लेकिन तुम सिर्फ वस्तु को परमाणु पहुँच चाहो तो क्या आपके पास ऑब्जेक्ट के खिलाफ कॉलिंग कोड लॉक नहीं हो सका?

// quick and dirty example 
// there's almost certainly a better/cleaner way to do this 
lock (MyProperty) 
{ 
    // other threads can't lock the object while you're in here 
    MyProperty.Field1 = 2; 
    // do more stuff if you like, the object is all yours 
} 
// now the object is up-for-grabs again 
आदर्श नहीं है, लेकिन इतने लंबे समय के रूप में वस्तु की असीमित एक्सेस lock (MyProperty) वर्गों में निहित है तो इस दृष्टिकोण धागा सुरक्षित हो जाएगा

+0

सहमत हुए। मैंने सिंगलटन प्रश्न http://stackoverflow.com/questions/7095/is-the-c-static-constructor-thread-safe/7105#7105 – Zooba

+0

हाँ के जवाब में आप जो पूछ रहे हैं उसके लिए नमूना समाधान प्रदान किया है, मैं ' जैसा कि आपने वर्णन किया है, ऑब्जेक्ट लॉक करने के साथ शायद आप जायेंगे। धन्यवाद। – mmr

+0

अच्छा बिंदु, हालांकि अगर उसके पास 'पी' नामक कुछ 'माइप्रोपर्टी' थी और उसे दो थ्रेड कहा जाता था: 'p = new MyObject (12)' और 'p = new MyObject (5) 'तो * यह * एक्सेस सिंक्रनाइज़ हो जाएगी । हालांकि 'फ़ील्ड 1' सदस्य * कभी लॉक नहीं होगा। मुझे पूरा यकीन है कि आप यही कह रहे हैं, बस इसे अपने लिए समझने की कोशिश कर रहे हैं। – Snoopy

1

आपके द्वारा पोस्ट किए गए कोड उदाहरण में, एक प्राप्त कभी पूर्ववर्ती नहीं होता है।

एक और अधिक जटिल उदाहरण में:

MyProperty.Field1 = MyProperty.doSomething() + 2; 

और निश्चित रूप से यह मानते हुए कि तुमने किया था एक:

lock (mLock) 
{ 
    // stuff... 
} 

doSomething() में तो ताला कॉल के सभी नहीं से अधिक तुल्यकालन की गारंटी करने के लिए पर्याप्त होगा पूरी वस्तु। जैसे ही doSomething() फ़ंक्शन रिटर्न हो जाता है, लॉक खो जाता है, फिर जोड़ा जाता है, और फिर असाइनमेंट होता है, जो फिर से लॉक हो जाता है।

या, यह एक और तरीका है आप ताले amutomatically नहीं किया हैं की तरह का नाटक कर सकते हैं, और अधिक की तरह प्रत्येक पंक्ति में एक आपरेशन के साथ "मशीन कोड" इस पुनर्लेखन लिखने के लिए, और यह स्पष्ट हो जाता है:

lock (mLock) 
{ 
    val = doSomething() 
} 
val = val + 2 
lock (mLock) 
{ 
    MyProperty.Field1 = val 
} 
+0

शिक। हो सकता है कि मैं गुणों को गलत समझ रहा हूं, लेकिन डीबगर निश्चित रूप से ऐसा लगता है कि यह 'get' फ़ंक्शन में कदम उठा रहा है, खासकर जब मैं वहां एक सरल असाइनमेंट के लिए ब्रेकपॉइंट डालता हूं। – mmr

+0

मैं स्वीकार करूंगा कि मुझे 100% यकीन नहीं है कि यह एक प्राप्त नहीं करता है, लेकिन मुझे कोई कारण नहीं दिखता कि यह क्यों होगा। यदि यह प्राप्त करने का आह्वान करता है, तो मैंने जो भी कहा है, वह लागू होता है, बस अपने काम के साथ doSomething() को प्रतिस्थापित करें। – SoapBox

+0

यह हर बार एक प्राप्त करने का आह्वान करता है। अन्यथा संदर्भ कहां से आएगा? –

1

मल्टीथ्रेडिंग की सुंदरता यह है कि आप नहीं जानते कि कौन सी ऑर्डर चीजें घटित होंगी। अगर आप एक थ्रेड पर कुछ सेट करते हैं, तो यह पहले हो सकता है, यह मिलने के बाद हो सकता है।

आपके द्वारा पोस्ट और पोस्ट किए जाने पर सदस्य को लॉक करने के साथ पोस्ट किया गया कोड। यदि आप उस मामले को संभालना चाहते हैं जहां मान अपडेट किया गया है, तो शायद आपको events जैसे सिंक्रनाइज़ेशन के अन्य रूपों को देखना चाहिए। (ऑटो/मैनुअल संस्करण देखें)। फिर आप अपना "मतदान" धागा बता सकते हैं कि मूल्य बदल गया है और यह फिर से तैयार होने के लिए तैयार है।

2

आपके उदाहरण में लॉक स्कोप गलत जगह पर है - इसे इसके कंटेनर की बजाय 'मायऑब्जेक्ट' श्रेणी की संपत्ति के दायरे में होना चाहिए।

यदि MyObject मेरी ऑब्जेक्ट क्लास का उपयोग केवल उस डेटा को रखने के लिए किया जाता है जो एक थ्रेड लिखना चाहता है, और दूसरा (यूआई थ्रेड) तब से पढ़ने के लिए आपको एक सेटटर की आवश्यकता नहीं हो सकती है और इसे एक बार बना सकते हैं।

यह भी विचार करें कि संपत्ति स्तर पर ताले लगाकर लॉक ग्रैन्युलरिटी का लेखन स्तर है; यदि एक लेनदेन की स्थिति का प्रतिनिधित्व करने के लिए एक से अधिक संपत्ति लिखी जा सकती है (उदाहरण: कुल आदेश और कुल वजन) तो माइक्रॉजेक्ट स्तर (यानी लॉक (myObject.SyncRoot) पर लॉक होना बेहतर हो सकता है .. ।)

0

आपके संपादित संस्करण में, आप अभी भी MyObject को अपडेट करने के लिए थ्रेडसेफ तरीका प्रदान नहीं कर रहे हैं। ऑब्जेक्ट के गुणों में किए गए किसी भी बदलाव को सिंक्रनाइज़/लॉक ब्लॉक के अंदर करने की आवश्यकता होगी।

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

12

यदि आपका दृष्टिकोण काम कर सकता है तो समवर्ती प्रोग्रामिंग बहुत आसान होगी। वहाँ भी बदतर होते हैं

objectRef.MyProperty += 1; 

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

+0

यह शानदार नहीं होगा अगर यह आसान हो सकता है? मुझे यकीन है कि ऐसा क्यों नहीं है, लेकिन मुझे यह जानने के लिए पर्याप्त विवरण नहीं पता कि कोड इस तरह से क्यों काम नहीं करता है। – mmr

+0

यह समझना महत्वपूर्ण है, यदि आप नहीं करते हैं तो समवर्ती प्रोग्रामिंग का प्रयास न करें। Google "परमाणु अद्यतन"। –

+0

मैं परमाणुता को समझता हूं, लेकिन मैं इस बात पर आलसी हूं कि सी # में चीजें परमाणु हैं। बस बुनियादी प्रकार के असाइनमेंट, है ना? इंटरलाक्ड जैसे कुछ के साथ परमाणु निर्दिष्ट करना उपयोगी हो सकता है ... – mmr

4

जैसा कि अन्य ने इंगित किया है, एक बार जब आप गेटटर से ऑब्जेक्ट वापस कर लेते हैं, तो आप ऑब्जेक्ट तक पहुंचने पर नियंत्रण खो देते हैं और कब। ऐसा करने के लिए जो आप करना चाहते हैं, आपको ऑब्जेक्ट के अंदर लॉक डालना होगा।

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

मैं डिवाइस की स्थिति को मतदान करने के लिए थ्रेड का उपयोग करने के बजाय एक ईवेंट का उपयोग करने का सुझाव भी दूंगा। मतदान तंत्र के साथ, जब भी थ्रेड डिवाइस को क्वेरी करता है तो आप लॉक को मारने जा रहे हैं। घटना तंत्र के साथ, स्थिति बदल जाने के बाद, वस्तु किसी भी श्रोताओं को सूचित करेगी। उस समय, आपका 'मतदान' धागा (जो अब मतदान नहीं करेगा) जाग जाएगा और नई स्थिति प्राप्त करेगा। यह बहुत अधिक कुशल होगा।

एक उदाहरण के रूप ...

public class Status 
{ 
    private int _code; 
    private DateTime _lastUpdate; 
    private object _sync = new object(); // single lock for both fields 

    public int Code 
    { 
     get { lock (_sync) { return _code; } } 
     set 
     { 
      lock (_sync) { 
       _code = value; 
      } 

      // Notify listeners 
      EventHandler handler = Changed; 
      if (handler != null) { 
       handler(this, null); 
      } 
     } 
    } 

    public DateTime LastUpdate 
    { 
     get { lock (_sync) { return _lastUpdate; } } 
     set { lock (_sync) { _lastUpdate = value; } } 
    } 

    public event EventHandler Changed; 
} 

आपका 'मतदान' धागा कुछ इस तरह दिखेगा।

Status status = new Status(); 
ManualResetEvent changedEvent = new ManualResetEvent(false); 
Thread thread = new Thread(
    delegate() { 
     status.Changed += delegate { changedEvent.Set(); }; 
     while (true) { 
      changedEvent.WaitOne(Timeout.Infinite); 
      int code = status.Code; 
      DateTime lastUpdate = status.LastUpdate; 
      changedEvent.Reset(); 
     } 
    } 
); 
thread.Start(); 
+0

दिलचस्प। मुझे इसे देखना होगा। मेरी पहली छाप यह है कि यह काम नहीं करेगा, क्योंकि जिस डिवाइस का मैं उपयोग कर रहा हूं वह तब तक एक स्टेटस संदेश नहीं देगा जब तक कि पूछा न जाए। तो अगर यह 'प्रीपे' मोड में है, तो जब तक आप पूछें तब तक कोई संकेत नहीं है। – mmr

+0

हाँ, अगर आपको स्थिति की रिपोर्ट करने के लिए डिवाइस को प्रोडक्ट करना है, तो मतदान केवल एकमात्र चीज है जो आप कर सकते हैं। मौके से, डिवाइस को समय-समय पर रिपोर्ट करने के लिए कोई कॉलबैक तंत्र है? मुझे मतदान से नफरत है क्योंकि यह अक्षम है, लेकिन कभी-कभी, आपके पास कोई विकल्प नहीं है। –

+0

दुर्भाग्यवश, यह एक वास्तविक धारावाहिक डिवाइस है - यह केवल मतदान के लिए जवाब देता है। यह यूएसबी नहीं है, यह 9-पिन है। पुराना स्कूल रेट्रो सभी तरह से, मुझे लगता है। – mmr

-1

करता है सी # ताले तो अन्य भाषाओं के रूप में ही ताला मुद्दों से ग्रस्त नहीं:

मैं सी # में अपरिवर्तनीय मॉडल वर्गों है कि इस संदर्भ में दिलचस्प हो सकता है पर एक लेख लिखा है?

ईजी।

var someObj = -1; 

// Thread 1 

if (someObj = -1) 
    lock(someObj) 
     someObj = 42; 

// Thread 2 

if (someObj = -1) 
    lock(someObj) 
     someObj = 24; 

यह दोनों धागे की समस्या को अंततः अपने ताले और मूल्य बदलने में समस्या हो सकती है। यह कुछ अजीब कीड़े का कारण बन सकता है। हालांकि आप ऑब्जेक्ट को अनावश्यक रूप से लॉक नहीं करना चाहते हैं जब तक कि आपको आवश्यकता न हो। इस मामले में आपको डबल चेक लॉकिंग पर विचार करना चाहिए।

// Threads 1 & 2 

if (someObj = -1) 
    lock(someObj) 
     if(someObj = -1) 
      someObj = {newValue}; 

बस कुछ ध्यान में रखना है।

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