2013-02-21 14 views
6

に一つだけの書き込みで、アップへの日付値インターロック変数からを読む:私は2つのメソッドを持つクラスを作成したい変数

  • void SetValue(T value)店舗値が、一つだけを保存することができますそれ以外の場合は例外がスローされます。
  • T GetValue()は値を取得します(まだ値がない場合は例外をスローします)。

私は、次の欲望/制約がある:値を読み込む

  • は安価でなければなりません。
  • 値を書くことは(適度に)高価なことがあります。それは別のスレッドでSetValue()への呼び出し後に古いnull値に基づいて例外をスローしないでください。
  • GetValue()は、最新の値が(null)存在しない場合にのみ、例外をスローする必要があります。
  • 値は1回だけ書き込まれます。これは、GetValue()がヌルでない場合、値をフレッシュにする必要がないことを意味します。
  • 完全なメモリバリアが回避できれば、それは(ずっと)良いことです。
  • ロックフリーの並行処理が優れていますが、ここに該当するかどうかはわかりません。

私はこれを達成するためのいくつかの方法を考え出しましたが、どちらが正しいか、効率的か、正しいか、効率的か、そしてより良い方法があるかはわかりません私が望むものを達成する。これは、(おそらく間違っている)を前提に依存しているフィールド

  • からの読み取りにInterlocked.CompareExchangeを使用したフィールド
  • への書き込みにInterlocked.CompareExchangeを使用し
  • 非揮発性フィールドを使用して

    方法1

    • フィールド上で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

    • Ìnterlocked.CompareExchangeto 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フィールド
    • を使用)。
    • フィールドは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

    • 書き込み時にロックを使用する。
    • nullを読み取ると、読み取り時にロックを使用します(参照がアトミックに読み取られるため、ロックの外側に一貫した値が得られます)。ロックを使用して値を書き込む
    • 揮発性フィールドを使用

      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

      コード。

    • フィールドがvolatileであるため、値を直接読み込みます(参照がアトミックに読み取られるため、ロックを使用しない場合でもコヒーレントな値が得られます)。

    コード:

    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

    私は 'ReaderWriterLock'を忘れてしまったので、私はそれをロックします。しかし、通常の 'lock'と同じように、読み込みごとに同期のオーバーヘッドが導入されると思います。なぜなら値は一度しか変更されないので避けたいのです。つまり、null以外の値(ロックなし)それが失効していないことを保証する。 –

    +0

    ReaderWriterLock(Slim)は、複数の同時読み取りが可能なため、実際には順序ロックより優れています。 – Woodman

    答えて

    0

    lock(#3)以外の方法は正しく動作しません。

    ルック:lock内部でない場合

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

    このコードは、スレッドセーフアトミックではありませんし、ありません。他のスレッドセット_valuenullは、ifreturnの間でまだ可能です。あなたができることは、ローカル変数に設定することです:

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

    しかし、それでも失速の値を返す可能性があります。 lockを使いやすく、簡単で簡単です。

    編集は:thisが外に表示され、サードパーティのコードは、オブジェクト上のlockに決めることができるので、lock(this)を使用しないでください。

    編集2: NULL値が設定することはできません場合だけ実行します。

    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

    ロックターゲットとして* this *を使用することは悪い考えです(lock(this))。この目的のために別のプライベートオブジェクトフィールドを宣言することは安全です。 – Woodman

    +0

    @Woodman、感謝します。 – Andrey

    +0

    いいえ、もう1つは '_value'をnullに設定できません:私の場合は' _value'が初期化の後に一度だけ書き込まれるという点で特別です。 'null'に初期化され、ある時点で非ヌル値に設定され、変更されません。 –

    1

    まあ、揮発性がわからないが、しかし、あなたは少し虐待を気にして第二の方法を起動していない場合。 ..(null許容性にも依存せず、値の型に自由に使用できます)ゲッターでのヌルチェックも避けてください。書き込みのみでロックが行われるため、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(); 
        } 
    } 
    

    実際、私はこれについて本当に馬鹿だと感じ、少し罵倒します。私はこれを行う潜在的な問題についてのコメントを見て好奇心が強いだろう。 :)

    EDIT:これは最初の呼び出しがSetValue(null)を意味することを意味しています。「null」は有効な値とみなされ、例外なく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

    @ GeorgesDupron私はボラティリティを意味しています。おそらく、それは 'ValueGetter'フィールドをvolatileとマークすることで解決できますか?正直言って、私は本当に揮発性のフィールドで作業する必要はありませんでした(私は 'lock'ブロックを中心にしています)。' SetValue'が実行されている間に 'GetValue'を打つと、あなたはもう何をしますかその時点で?編集:誰かがフィールドを「揮発性」とマークすることが確認できれば、あるいは「陳腐化」可能性のある問題を避けるために、私は喜んで答えを編集します。 –

    関連する問題