2010-11-20 8 views
3

安全に更新したい静的な辞書があります。最初は、辞書は空になりますが、アプリの存続期間中は辞書に新しい値が追加されます。また、整数値はインクリメントとデクリメントが可能な個別のカウンタとして機能します。.NET4.0:スレッドを安全に更新する辞書とその値

private static Dictionary<string, int> foo = new Dictionary<string, int>(); 

public static void Add(string bar) 
{ 
    if (!foo.ContainsKey(bar)) 
     foo.Add(bar, 0); 

    foo[bar] = foo[bar] + 1; 
} 


public static void Remove(string bar) 
{ 
    if (foo.ContainsKey(bar)) 
    { 
     if (foo[bar] > 0) 
      foo[bar] = foo[bar] - 1; 
    } 
} 

私は、スレッドの安全性を提供するための手段としてInterlocked class上に読んでいると、私が使用できるものであってもよいことに表示されます。しかし

public static void Add(string bar) 
{ 
    if (!foo.ContainsKey(bar)) 
     foo.Add(bar, 0); 

    Interlocked.Increment(ref foo[bar]); 
} 


public static void Remove(string bar) 
{ 
    if (foo.ContainsKey(bar)) 
    { 
     if (foo[bar] > 0) 
      Interlocked.Decrement(ref foo[bar]); 
    } 
} 

、私の本能私は実際に辞書に新しい項目を追加しているときに問題を解決できないので、すぐにロックすることになります:

private static Dictionary<string, int> foo = new Dictionary<string, int>(); 
private static object myLock = new object(); 

public static void Add(string bar) 
{ 
    lock(myLock) 
    { 
     if (!foo.ContainsKey(bar)) 
      foo.Add(bar, 0); 

     Interlocked.Increment(ref foo[bar]); 
    } 
} 


public static void Remove(string bar) 
{ 
    lock(myLock) 
    { 
     if (foo.ContainsKey(bar)) 
     { 
      if (foo[bar] > 0) 
       Interlocked.Decrement(ref foo[bar]); 
     } 
    } 
} 

このアプローチは理想的ですか、それとも正しいですか?それは改善することができますか?私はオフですか?

+0

Interlocked.Increment(ref foo [bar])を使用することも可能です。 は、インデクサーまたはプロパティでは機能しないというエラーが表示されますか? –

答えて

3

lockが良いです(必要であれば、Interlockedで十分ではないと思われます)が、一度すればInterlocked.IncrementInterlocked.Decrementは不要です。複数のスレッドからDictionary<TKey, TValue>にアクセスする際の問題は、あるスレッドが内部ハッシュテーブルの再構築をトリガできることです。そのスレッドは、今度は来る別のスレッドの中間再構築からスワップアウトされ、辞書の内部構造を破壊してしまいます。辞書

さらに、thisではなく、オブジェクトのlockが実装されています。 chibacityが指摘しているように、このlockオブジェクトはreadonlyである必要があります。

マルチスレッドのシナリオであなたの辞書を防弾にしたと思うように騙されないように注意してください。たとえば、次のように起こることができる:

スレッド1は辞書で文字列"Hello, World!"を検索し、数1をバック受け取ります。

スレッド1がスレッド2回の通話が0にカウント数を設定キー"Hello, World!"を削除2.

スレッドのためにスワップアウトされます。

スレッド2は現在カウントが1ですが、それが実際に0だと思いスレッド1

スレッド1のためにスワップアウトされます。

最後に、.NET 4.0では、ConcurrentDictionary<TKey, TValue>の使用を検討する必要があります。これは辞書のインスタンスメソッドを呼び出すときにlockの必要性を実質的に排除するが、上記のシナリオが発生するのを排除するものではないことに注意されたい。

+0

私は.NET 4.0を使用しているので、コンセンサスは私のDictionaryをConcurrentDictionaryに置き換えなければならないと思われます。これは仕事をするように見える。しかし、上記のシナリオはどのように回避されますか? – Bullines

3

.Net 4.0を使用している場合は、ConcurrentDictionary < TKey、TValue >などのSystem.Collections.Concurrent名前空間からのコレクションの使用について考えることができます。

+0

私は本当に.Net 4.0を使用しています。 ystem.Collections.Concurrentが提供しているものを見てみましょう。 – Bullines

1

あなたの場合、辞書のadd \ remove操作はスレッドセーフでなければなりません。これは、あるスレッドで辞書を列挙している場合は、他のスレッドがそのスレッドを変更しないようにするためです。あなたはプライベートたIDictionary <で内部的にデータを保持した辞書のラッパーを作成する必要がありますスレッドセーフな辞書を作成するには

>と、追加、削除、クリアなどのような方法でロックを使用

このSO記事を参照してください。それを行う方法について - What's the best way of implementing a thread-safe Dictionary?

また、.Net 4を使用している場合は、ConcurrentDictionaryを使用すると、すぐにスレッドの安全性を確保できます。

3

ロックを使用してAddメソッドとRemoveメソッドで辞書へのマルチスレッドアクセスを守っているので、このコードがこれらのロックのスコープ内にあるため、インターロックステートメントは不要です。ロックブロック内のすべてのコードは、現在、シングルスレッドで安全であることが保証されています。

小さな点ですが、現在は再割り当てすることでこのオブジェクトを変更することができるため、myLockオブジェクトを読み取り専用としてマークする必要があります。したがって、あるスレッドがロックしている間にオブジェクトを変更することができ、後続のスレッドは別のロックオブジェクトを参照するため、以前のスレッドロックを無視することができます。オブジェクトを読み取り専用としてマークすると、そのオブジェクトは不変になります。

+1

私はロックオブジェクトを読み取り専用にする考えが好きです:) – Bullines

0

Monitor.Enter()、Monitor.Exit()(lock(object))はおそらく最善の策に近いでしょう。ただし、このコードはいくらか改善することができますが、実際のメリットは重要ではありません。削除のために同じことをするだろう

 private static Dictionary<string, int> foo = new Dictionary<string, int>(); 
    private static ReaderWriterLock rwLock= new ReaderWriterLock(); 
    static int reads = 0; 
    static int writes = 0; 


    private static bool Contains(string bar) 
    { 
      try 
      { 
       rwLock.AquireReaderLock(TimeOut.Infinite); 
       InterLocked.Increment(ref reads); 
       return foo.ContainsKey(bar); 


      } 
      catch(Exception) 
      { 


      } 
      finally 
      { 
       rwLock.ReleaseReaderLock(); 
      } 
    } 

    public static void Add(string bar) 
    { 
     try 
     { 
      rwLock.AquireWriterLock(TimeOut.Infinite); 

      if (!ContainsKey(bar)) 
      { 
       foo.Add(bar, 0); 
      } 
      foo[bar] = foo[bar] + 1; 
      Interlocked.Increment(ref writes); 
     } 
     catch(Exception) {} 
     finally 
     { 
       rwLock.ReleaseWriterLock(); 
     } 

}。

関連する問題