2013-02-21 29 views
6

पर केवल एक ही लिखने के साथ मैं दो तरीकों के साथ एक वर्ग बनाने के लिए करना चाहते हैं:पढ़ें एक अप-टू एक Interlocked चर से दिनांक मान, चर

  • void SetValue(T value) भंडार एक मूल्य है, लेकिन केवल एक ही भंडारण की अनुमति देता है मूल्य (अन्यथा यह एक अपवाद फेंकता है)।
  • T GetValue() मान प्राप्त करता है (और अगर कोई मूल्य अभी तक है एक अपवाद फेंकता है)।

    • मूल्य पढ़ना सस्ता होना चाहिए:

    मैं निम्नलिखित इच्छाओं/कमी की है।

  • मूल्य लिखना (मामूली) महंगा हो सकता है।
  • GetValue() अपवाद फेंक चाहिए तभी जब अप-टू-डेट मूल्य अनुपस्थित (null) है: यह एक और धागा में SetValue() के लिए एक कॉल के बाद एक बासी null मूल्य के आधार पर एक अपवाद फेंक नहीं करना चाहिए।
  • मूल्य केवल एक बार लिखा जाता है। इसका मतलब है कि GetValue() को मान को ताज़ा करने की आवश्यकता नहीं है यदि यह शून्य नहीं है।
  • यदि एक पूर्ण मेमोरी बाधा से बचा जा सकता है, तो यह (अधिक) बेहतर है।
  • मुझे लगता है कि ताला मुक्त संगामिति बेहतर है मिलता है, लेकिन मुझे यकीन है कि अगर यह मामला यहाँ है नहीं कर रहा हूँ।

मैं इसे प्राप्त करने के कई तरीकों से आया हूं, लेकिन मुझे यकीन नहीं है कि कौन सा सही है, जो कुशल हैं, वे सही (और) कुशल क्यों हैं, और यदि कोई बेहतर तरीका है जो मैं चाहता हूं उसे प्राप्त करने के लिए।

विधि 1

  • क्षेत्र
  • क्षेत्र से पढ़ने के लिए Interlocked.CompareExchange का उपयोग करने के लिए लिखने के लिए एक गैर अस्थिर क्षेत्र
  • Interlocked.CompareExchange का उपयोग का उपयोग करना
  • यह (संभवतः गलत) धारणा पर निर्भर करता है कि किसी क्षेत्र पर Interlocked.CompareExchange(ref v, null, null) करने के बाद परिणामस्वरूप अगले मूल्यों को प्राप्त करने के परिणामस्वरूप कम से कम Interlocked.CompareExchange देखा गया होगा।

कोड:

public class SetOnce1<T> where T : class 
{ 
    private T _value = null; 

    public T GetValue() { 
     if (_value == null) { 
      // Maybe we got a stale value (from the cache or compiler optimization). 
      // Read an up-to-date value of that variable 
      Interlocked.CompareExchange<T>(ref _value, null, null); 
      // _value contains up-to-date data, because of the Interlocked.CompareExchange call above. 
      if (_value == null) { 
       throw new System.Exception("Value not yet present."); 
      } 
     } 

     // _value contains up-to-date data here too. 
     return _value; 
    } 

    public T SetValue(T newValue) { 
     if (newValue == null) { 
      throw new System.ArgumentNullException(); 
     } 

     if (Interlocked.CompareExchange<T>(ref _value, newValue, null) != null) { 
      throw new System.Exception("Value already present."); 
     } 

     return newValue; 
    } 
} 

विधि 2

  • एक volatile क्षेत्र
  • का उपयोग का उपयोग करना Ìnterlocked.CompareExchange to write the value (with [Joe Duffy](http://www.bluebytesoftware.com/blog/PermaLink,guid,c36d1633-50ab-4462-993e-f1902f8938cc.aspx)'s #pragma to avoid the compiler warning on passing a volatile value by ref`)।
  • मूल्य पढ़ना सीधे, के बाद से क्षेत्र volatile

कोड है:

public class SetOnce2<T> where T : class 
{ 
    private volatile T _value = null; 

    public T GetValue() { 
     if (_value == null) { 
      throw new System.Exception("Value not yet present."); 
     } 
     return _value; 
    } 

    public T SetValue(T newValue) { 
     if (newValue == null) { 
      throw new System.ArgumentNullException(); 
     } 

     #pragma warning disable 0420 
     T oldValue = Interlocked.CompareExchange<T>(ref _value, newValue, null); 
     #pragma warning restore 0420 

     if (oldValue != null) { 
      throw new System.Exception("Value already present."); 
     } 
     return newValue; 
    } 
} 

विधि 3

  • एक गैर अस्थिर क्षेत्र का उपयोग करना।
  • लिखते समय लॉक का उपयोग करना।
  • पढ़ते समय लॉक का उपयोग करना अगर हम शून्य पढ़ते हैं (हमें लॉक के बाहर एक सुसंगत मूल्य मिलेगा क्योंकि संदर्भ परमाणु रूप से पढ़ा जाता है)।

कोड:

public class SetOnce3<T> where T : class 
{ 
    private T _value = null; 

    public T GetValue() { 
     if (_value == null) { 
      // Maybe we got a stale value (from the cache or compiler optimization). 
      lock (this) { 
       // Read an up-to-date value of that variable 
       if (_value == null) { 
        throw new System.Exception("Value not yet present."); 
       } 
       return _value; 
      } 
     } 
     return _value; 
    } 

    public T SetValue(T newValue) { 
     lock (this) { 
      if (newValue == null) { 
       throw new System.ArgumentNullException(); 
      } 

      if (_value != null) { 
       throw new System.Exception("Value already present."); 
      } 

      _value = newValue; 

      return newValue; 
     } 
    } 
} 

विधि 4

  • एक अस्थिर क्षेत्र
  • एक ताला का उपयोग कर मूल्य लेखन का उपयोग करना।
  • सीधे मूल्य पढ़ना, क्योंकि क्षेत्र अस्थिर है (अगर हम लॉक का उपयोग नहीं करते हैं तो भी हम एक सुसंगत मूल्य प्राप्त करेंगे क्योंकि संदर्भ परमाणु रूप से पढ़ा जाता है)।

कोड:

public class SetOnce4<T> where T : class 
{ 
    private volatile T _value = null; 

    public T GetValue() { 
     if (_value == null) { 
      throw new System.Exception("Value not yet present."); 
     } 
     return _value; 
    } 

    public T SetValue(T newValue) { 
     lock (this) { 
      if (newValue == null) { 
       throw new System.ArgumentNullException(); 
      } 

      if (_value != null) { 
       throw new System.Exception("Value already present."); 
      } 

      _value = newValue; 

      return newValue; 
     } 
    } 
} 

अन्य तरीकों

मैं भी तकनीक लेखन से किसी के साथ Thread.VolatileRead() इस्तेमाल कर सकते हैं मूल्य को पढ़ने के लिए संयोजन में,।

+1

आप एक 'ReaderWriterLock (स्लिम)' का उपयोग कर विचार किया है? क्या आप सुनिश्चित हैं कि एक साधारण 'लॉक' आपकी आवश्यकताओं को पूरा नहीं करता है? क्यों नहीं? 'SetOnce' कक्षा के लिए उपयोग पैटर्न क्या है? – dtb

+0

मैं 'रीडरवाइटर लॉक' के बारे में भूल गया था, मैं इसे लॉक दूंगा। लेकिन नियमित रूप से 'लॉक' की तरह, मुझे लगता है कि यह प्रत्येक पठन पर एक सिंक्रनाइज़ेशन ओवरहेड पेश करेगा, जिसे मैं तब से टालना चाहूंगा क्योंकि मान केवल एक बार बदल गया है, इसलिए जब गैर-शून्य मान (ताले के बिना) पढ़ते हैं, तो किसी के पास गारंटी है कि यह पुराना नहीं है। –

+0

रीडरवाइटर लॉक (स्लिम) वास्तव में बेहतर है तो सामान्य लॉक, क्योंकि यह एकाधिक एक साथ पढ़ने की अनुमति देता है। – Woodman

उत्तर

0

lock (# 3) के साथ किसी भी को छोड़कर आपकी कोई भी विधि ठीक से काम नहीं करेगी।

देखो:

if (_value == null) { 
     throw new System.Exception("Value not yet present."); 
    } 
    return _value; 

इस कोड को परमाणु नहीं है और थ्रेड-सुरक्षित नहीं, नहीं अंदर lock है। यह अभी भी संभव है कि अन्य धागे _valuenullif और return के बीच सेट करें। क्या तुम कर सकते हो स्थानीय चर करने के लिए सेट करने के लिए है:

var localValue = _value; 
    if (localValue == null) { 
     throw new System.Exception("Value not yet present."); 
    } 
    return localValue; 

लेकिन फिर भी यह स्टाल मान सकता है। आप lock का बेहतर उपयोग करेंगे - स्पष्ट, आसान और तेज़।

संपादित: lock(this) प्रयोग करने से बचें, क्योंकि this बाहर दिखाई दे रहा है और तीसरे पक्ष के कोड अपने वस्तु पर lock का फैसला कर सकता।

संपादित करें 2: शून्य मान तो कभी नहीं सेट किया जा सकता है, तो बस करो:

public T GetValue() { 
    if (_value == null) { 
     throw new System.Exception("Value not yet present."); 
    } 
    return _value; 
} 

public T SetValue(T newValue) { 
    lock (writeLock) 
    {   
     if (newValue == null) { 
      throw new System.ArgumentNullException(); 
     } 
     _value = newValue; 
     return newValue; 
    } 
} 
+0

नोटिस, * यह * लॉक लक्ष्य के रूप में उपयोग करना एक बुरा विचार है (लॉक (यह)), इस उद्देश्य के लिए अलग निजी ऑब्जेक्ट फ़ील्ड घोषित करना सुरक्षित है – Woodman

+0

@ वुडमैन, धन्यवाद, मैं इसका उल्लेख करना चाहता था, लेकिन भूल गया। – Andrey

+0

नहीं, कोई दूसरा '_value' को शून्य पर सेट नहीं कर सकता है: मेरा मामला उस' _value' में प्रारंभिक है, इसके आरंभ होने के बाद केवल एक बार लिखा गया है। इसे 'शून्य' में प्रारंभ किया गया है, फिर कुछ समय पर इसे गैर-शून्य मूल्य पर सेट किया जाएगा, फिर कभी संशोधित नहीं किया जाता है। –

1

ठीक है, अस्थिरता के बारे में निश्चित नहीं हैं, लेकिन आप एक छोटे से दुरुपयोग कोई आपत्ति नहीं है, तो और एक दूसरी विधि लागू। .. (यह भी शून्यता पर निर्भर नहीं है; मूल्य प्रकारों के लिए स्वतंत्र रूप से प्रयोग योग्य) गेटटर में भी शून्य जांच से बचें।केवल लेखन पर लॉकिंग किया जाता है, इसलिए AFAIK, मूल्य प्राप्त करते समय प्रतिनिधि का आह्वान करने का एकमात्र नकारात्मक प्रभाव आता है।

public class SetOnce<T> 
{ 
    private static readonly Func<T> NoValueSetError =() => { throw new Exception("Value not yet present.");}; 

    private Func<T> ValueGetter = NoValueSetError; 
    private readonly object SetterLock = new object(); 

    public T SetValue(T newValue) 
    { 
     lock (SetterLock) 
     { 
      if (ValueGetter != NoValueSetError) 
       throw new Exception("Value already present."); 
      else 
       ValueGetter =() => newValue; 
     } 

     return newValue; 
    } 

    public T GetValue() 
    { 
     return ValueGetter(); 
    } 
} 

वास्तव में, मैं वास्तव में इस बारे में मूर्खतापूर्ण महसूस करता हूं और थोड़ा अपमानजनक महसूस करता हूं। ऐसा करने के संभावित मुद्दों के बारे में टिप्पणियां देखने के लिए उत्सुकता होगी। :)

संपादित करें: बस एहसास हुआ कि इसका मतलब है कि SetValue(null) पर पहला कॉल का अर्थ है कि "शून्य" को वैध मान माना जाएगा और अपवाद के बिना शून्य वापस आ जाएगा। सुनिश्चित नहीं है कि यह वही है जो आप चाहते हैं (मुझे नहीं लगता कि null मान्य मान नहीं हो सकता है, लेकिन यदि आप इसे टालना चाहते हैं तो बसटर में चेक करें; गेटटर में कोई आवश्यकता नहीं है)

EDITx2: आप अभी भी class करने के लिए इसे बाधित करते हैं और null मूल्यों से बचने के लिए चाहते हैं, एक सरल परिवर्तन हो सकता है:

public class SetOnce<T> where T : class 
{ 
    private static readonly Func<T> NoValueSetError =() => { throw new Exception("Value not yet present.");}; 

    private Func<T> ValueGetter = NoValueSetError; 
    private readonly object SetterLock = new object(); 

    public T SetValue(T newValue) 
    { 
     if (newValue == null) 
      throw new ArgumentNullException("newValue"); 

     lock (SetterLock) 
     { 
      if (ValueGetter != NoValueSetError) 
       throw new Exception("Value already present."); 
      else 
       ValueGetter =() => newValue; 
     } 

     return newValue; 
    } 

    public T GetValue() 
    { 
     return ValueGetter(); 
    } 
} 
+0

मुख्य समस्या जो मैं देखता हूं वह है कि GetValue में आप किसी अन्य थ्रेड में SetValue कहने के बाद एक पुराना मान (NoValueSetError) प्राप्त कर सकते हैं। इसके अलावा, आपका समाधान काफी मजाकिया और कल्पनाशील है :) मुझे इसे पढ़ने का आनंद लिया! –

+0

@ जॉर्जेस डुपेरॉन हाँ, यही मेरा अस्थिरता के बारे में है। शायद 'ValueGetter' फ़ील्ड को अस्थिर के रूप में चिह्नित करके हल किया जा सकता है? ईमानदारी से, मुझे अस्थिर क्षेत्रों के साथ वास्तव में काम करने की ज़रूरत नहीं है (मैं मुख्य रूप से 'लॉक' ब्लॉक से चिपक जाता हूं) मुझे लगता है कि अगर आप 'GetValue' को मारने के दौरान' GetValue 'दबाते हैं, तो वैसे भी आप क्या करना चाहते हैं उस बिंदु पर? संपादित करें: अगर कोई यह पुष्टि कर सकता है कि फ़ील्ड को 'अस्थिर' या कुछ अन्य चिमटा के रूप में चिह्नित करना संभव है तो "बासी" समस्या से बचा जाता है, तो मैं खुशी से जवाब संपादित कर दूंगा। –