2017-02-25 8 views
0

私は、外部辞書の操作がコレクション全体への操作をロックするネストされた辞書へのアクセスを同期するための安全なアプローチを見つけようとしています。しかし、内部辞書が取り出されると、外部ロックを解放し、スレッドが内部辞書を操作しないようにしたい。辞書をロックして、安全に辞書の値にロックを渡す方法は?

内側の辞書が外側の辞書内に存在すると、これにはlockキーワードを使用するのは簡単ですが、競合条件を導入せずに内側の辞書を追加して設定するという安全なアプローチを見つけるのは苦労しています。内側の辞書を "取り込む"ことは高価な操作であると仮定します。このため、外部辞書のlockの下に置くのはオプションではありません。他のスレッドは、他の内部辞書に対する操作を実行するために外部辞書にアクセスしなければならず、一方、「内部」は新しい内部辞書に対して実行される。

私は私の質問をよりよく説明する以下の例を挙げました。私は競合状態

  • 私は仕事と考えているのアプローチを紹介すると信じて

    1. 些細なアプローチは、しかし、不必要に複数の内部辞書を取り込むにつながることができます。これは、レースで勝利した最初のインナー辞書を使用して終了します。
    2. 私は考えているアプローチは、しかし、それは不慣れな領域です。広範なテストをしても、C#のMonitorオブジェクトを使用した私の経験の不足が予期せぬ結果を招く恐れがあります。

    例1:私の問題は安全ではないと考えています。しかし、これは伝統的に同期しています。このアプローチを使用することをお勧めしますが、lockキーワードを使用して必要な動作を達成する方法がわかりません。

    public void SyncronizationExample1(Dictionary<TKey, Dictionary<TKey2, ReferenceTypedValues>> outerDictionary, TKey newKey) 
    { 
        Dictionary<TKey2, ReferenceTypedValues> innerDictionary = null; 
    
        lock (outerDictionary) 
        { 
         // No need to add a new innerDictionary, it already exists 
         if (outerDictionary.ContainsKey(newKey)) 
         { 
          return; 
         } 
    
         innerDictionary = new Dictionary<TKey2, ReferenceTypedValues>(); 
         outerDictionary.Add(newKey, innerDictionary); 
        } 
    
        // I want to allow other threads to have access to outerDictionary 
        // However, I don't want other threads working against THIS innerDictionary 
        lock (innerDictionary) 
        { 
         // Here lies my concern with this approach. Another thread could have 
         // taken the lock for innerDictionary. Doing this all under the lock 
         // for outerDictionary would be safe but would prevent access to other 
         // inner dictionaries while expensive operations are performed only 
         // pertaining to THIS innerDictionary 
         this.PopulateInnerDictionary(innerDictionary); 
        } 
    } 
    

    例2:複数のスレッドが同時に操作を試みた場合、不要な計算として理想的ではない、実施例1に描か問題が発生しないlockを用いてアプローチが起こり得ます。ただし、グローバルな "Populate"ロックに対してロックするようにこのアプローチを変更することができます。これにより、複数のスレッドが同時に異なる内部ディメンションに値を設定することを防ぐことができます。

    public void SyncronizationExample3(Dictionary<TKey, Dictionary<TKey2, ReferenceTypedValues>> outerDictionary, TKey newKey) 
    { 
        lock (outerDictionary) 
        { 
         if (outerDictionary.ContainsKey(newKey)) 
         { 
          // No need to add a new innerDictionary, it already exists 
          return; 
         } 
        } 
    
        var innerDictionary = new Dictionary<TKey2, ReferenceTypedValues>(); 
    
        // Expensive operation - if called by multiple threads at the same time 
        // multiple innerDictionaries will be populated but only the one to win 
        // the race will be utilized. 
        this.PopulateInnerDictionary(innerDictionary); 
    
        lock (this.outerDictionary) 
        { 
         if (!outerDictionary.ContainsKey(newKey)) 
         { 
          // If another thread won the race this newKey would be in outerDictionary 
          // The innerDictionary we just populated is redundant 
          outerDictionary.Add(newKey, innerDictionary); 
         } 
        } 
    } 
    

    例3:私は、実施例1に示された潜在的な同期の問題に対する解決策であると信じることはしかし、私はMonitorパターンを使用してに不慣れだと大幅にフィードバックをいただければ幸いです。

    public void SyncronizationExample3(Dictionary<TKey, Dictionary<TKey2, ReferenceTypedValues>> outerDictionary, TKey newKey) 
    { 
        Dictionary<TKey2, ReferenceTypedValues> innerDictionary = null; 
        bool aquiredLockForOuterDictionary = false; 
        bool aquiredLockForInnerDictionary = false; 
    
        try 
        { 
         Monitor.Enter(outerDictionary, ref aquiredLockForOuterDictionary); 
    
         if (outerDictionary.Contains(newKey) 
         { 
          // No need to add a new innerDictionary, it already exists 
          return; 
         } 
    
         innerDictionary = new Dictionary<TKey2, ReferenceTypedValues>(); 
         outerDictionary.Add(newKey, innerDictionary); 
    
         // This is where I "handoff" the lock to innerDictionary to alleviate my concern 
         // in Example 1 where another thread could steal the innerDictionary lock 
         Monitor.Enter(innerDictionary, ref aquiredLockForInnerDictionary); 
        } 
        finally 
        { 
         // I read that this bool pattern was preferred for .net 4+, 
         // however I am unsure if this is the best practice 
         if (aquiredLockForOuterDictionary) 
         { 
          Monitor.Exit(dictionary); 
         } 
        } 
        try 
        { 
         if (!aquiredLockForInnerDictionary) 
         { 
          // An exception must have occurred prior to or during the acquisition 
          // of this lock. Not sure how I'd handle this yet but 
          // I'm pretty shit out of luck. 
          return; 
         } 
    
         // Here I would perform an expensive operation against the innerDictionary 
         // I do not want to lock consumers form accessing other innerDictionaries 
         // while this computation is done. 
         this.PopulateInnerDictionary(innerDictionary); 
        } 
        finally 
        { 
         // I need to check this here incase an exception in the first 
         // try finally prevented this from being acquired 
         if (aquiredLockForInnerDictionary) 
         { 
          Monitor.Exit(innerDictionary); 
         } 
        } 
    } 
    
  • +0

    おそらく 'ConcurrentDictionary <>'を使用していますか? – MickyD

    答えて

    3

    あなたはそれを思い知らされているようです。私が持っている唯一の質問は、内部ディクショナリインスタンスにデータが入力されているかどうかを知る信頼できる方法がありますか? Countプロパティの値が0でないとすればいいでしょうか?

    public Dictionary<TKey2, ReferenceTypedValues> SyncronizationExample1(Dictionary<TKey, Dictionary<TKey2, ReferenceTypedValues>> outerDictionary, TKey newKey) 
    { 
        Dictionary<TKey2, ReferenceTypedValues> innerDictionary = null; 
    
        lock (outerDictionary) 
        { 
         // No need to add a new innerDictionary if it already exists 
         if (!outerDictionary.TryGetValue(newKey, out innerDictionary)) 
         { 
          innerDictionary = new Dictionary<TKey2, ReferenceTypedValues>(); 
          outerDictionary.Add(newKey, innerDictionary); 
         }  
        } 
    
        // Found the inner dictionary, but might be racing with another thread 
        // that also just found it. Lock and check whether it needs populating 
        lock (innerDictionary) 
        { 
         if (innerDictionary.Count == 0) 
         { 
          this.PopulateInnerDictionary(innerDictionary); 
         } 
        } 
    
        return innerDictionary; 
    } 
    

    注:

    • あなたが危険を冒すので、ロックオブジェクトとしてオブジェクト自体を使用することをお勧めではないと仮定して

      、あなたはこれを行うことができます他のいくつかのコードは同じ悪いアイデアを持ち、そのコードでデッドロックしています。代わりに、ロックに使用するobjectと内部辞書自体の両方を含むコンポジット値(たとえば、Tuple<object, Dictionary<...>>、カスタム名前付き構造体など)を格納します。 (そしてもちろん、単一の外部オブジェクトをロックするフィールドに1つの専用オブジェクトを格納してください)

    • これらのオブジェクトがどのようにしてが使用されているかについての説明はありません。私は一度人口が多いと推測している、彼らは読み取り専用ですか?もしそうなら、上記はうまくいくはずですが、そのためにはReadOnlyDictionaryクラスを使うべきです。それ以外の場合は、もちろん同期の追加も必要です。
    • 読み込み専用であっても、元のコードは実際に内部ディクショナリ参照を返しません。この例を少し修正して、そうするようにしました。 でなければなりません。上のコードのチェックを行わずに、参照を探しているコードを持つことはできません。最良の場合のシナリオでは、データ構造が一貫性のない状態にあるときにデータ構造にアクセスしようとするとクラッシュする最悪のシナリオがまだ完全には埋め込まれません。規約では、呼び出し元が参照値を取得する前に常に上記のメソッドを呼び出すように要求できますが、もしそれを行う場合は、メソッド自体から便利な値を返すようにすることもできます。
    +0

    タプルを使うことは、boolを追加するだけで済むので、辞書カウントについて何も仮定する必要がないという利点があります。 – Voo

    関連する問題