2011-01-14 3 views
10

に書く、読むの明確化とC#の辞書この文の文脈では

A辞書はコレクションが変更されないよう 限り、同時に 複数のリーダーをサポートすることができます。 それでも、 コレクションを列挙するのは、本質的にはスレッドセーフな手順ではありません。 まれに の列挙で、書き込みアクセスを伴う と競合する場合、 列挙全体では、 のコレクションをロックする必要があります。 に複数のスレッドでアクセスして の読み取りと書き込みを許可するには、 独自の同期を実装する必要があります。

読み書きは何を意味しますか?私の理解は、読み取りは、キーを検索し、その値への参照を提供する操作であり、書き込みは、キー値のペアを辞書から追加または削除する操作であるということです。しかし、私はこれに関して決定的な何かを見つけることはできません。

大きな問題は、スレッドセーフな辞書を実装する際に、辞書内の既存のキーの値を更新する操作は、読者またはライターと考えるべきでしょうか?私は辞書内の一意のキーにアクセスする複数のスレッドを持ち、それらの値を変更する予定ですが、スレッドは新しいキーを追加/削除しません。

既存の値を変更することが辞書の書き込み操作ではないとすれば、毎回排他ロックを取得する必要はないため、スレッドセーフな辞書の実装はずっと効率的になります私は既存のキーに値を更新しようとします。

.Net 4.0からConcurrentDictionaryを使用することはできません。既存の値を上書き

答えて

2

値を変更する書き込み操作

+2

これは本当にコメントです。質問に対する回答ではありません。著者にフィードバックを残すには、「コメントを追加」を使用してください。 – SliverNinja

+0

@ SliverNinjaは、この答えは、 "辞書の既存のキーの値を更新する操作は、読者または作家を考慮する必要がありますか?" – binki

-1

として扱われるべきであることは、ライトで、競合条件を紹介します。

[5] 837

何の価値べきであることを[5] mydict 112 別のスレッドを更新するのは、mydictの元の値が[5] mydict 42 1つのスレッドの更新を=ましょうmydict [5]最後にいるのですか?この場合、スレッドの順序は重要です。つまり、順序が明示的かどうか、または書き込みが行われないようにする必要があります。

+1

あなたが何を記述しているかは、本当に競争状態ではありません。それは誰でも勝手に勝つことができますし、アクセスをロックしても同じです。競合状態は、if(!dict.ContainsKey(5))dict(5、new Value())のようになります。ここで、潜在的な矛盾が発生します。 – Neil

+0

@ニール私はそれが競争状態だと思う、ちょうどその辞書が対処できる競争条件ではない。私は既に、回答に記載されている競合状態のタイプが起こらないことを保証するようにコーディングしています。 – Joshua

+3

@Joshua - 'mydict [5] = X'を実装するディクショナリコード内で競合状態が発生する可能性がありますが、Serinusの3つのset文の間に競合条件は存在しません。誰が価値を最終的に決定するのか - これは通常のプログラミングの説明に過ぎません。競合状態は、前回の以前の状態の検査に基づいて状態を設定した場合に発生します。それ自身ではx = 1は競合状態を生成することはできません。 – Neil

0

別の読み取りの結果に影響を与えるものはすべて書き込みと見なされます。

それは項目が内部ハッシュまたはインデックスまたはただし辞書が彼らのO((n)をログ)ものを行うに移動する原因となりますので、キーはあなたが望むかもしれないもの...ほとんど間違いなく

書き込みで変更値を更新http://msdn.microsoft.com/en-us/library/system.threading.readerwriterlock.aspx

+2

私が読んだドキュメントの多くは、Reader/Writerのロックが、単なるロックを使用するよりもパフォーマンスが驚くほど悪くないことを示しています。 ReaderWriterLockSlimは、使用可能な場合に使用するか、別のものを使用する必要があります。 http://msdn.microsoft.com/en-us/library/system.threading.readerwriterlockslim.aspx –

+0

@エリック - 私は同意しますが、追加します:それは常にバランスです。それは、ミックスの他のコストに依存しなければならないだろう。読み書きの量がほぼ等しい場合は、lock()を使用することをお勧めしますが、書き込みが稀であれば通常はリーダーのロックが有効です。彼らがアップグレードするときスリムロックに移動することは簡単になります。問題が発生したときのプロファイリングは、パフォーマンスを得るための最良の方法です。多くの場合、1回の操作でミリ秒を節約するためにコーディングに日数を費やします。 – Neil

+0

合意した、時には開発者は過度に事前に最適化しようと過ごす。私はそれが微妙なパフォーマンスの問題ではなくむしろ劇的なものであるように見えることを「衝撃的に悪いパフォーマンス」という表現を使用しました。通常は、使用するよりもむしろ避ける方が良いと考えられています。少なくとも、それは私が様々なパフォーマンスの議論の間に到達した結論の多くであるようです。 –

0

ReaderWriterLock

を見ている行うには、概念的には、書き込み動作です。書き込みが完了する前に読み取りが実行される同時アクセスで値を更新する場合、古い値を読み取ります。 2つの書き込みが競合すると、間違った値が格納されることがあります。

新しい値を追加すると、基になるストレージが大きくなる可能性があります。この場合、新しいメモリが割り当てられ、すべての要素が新しいメモリにコピーされ、新しい要素が追加され、ディクショナリオブジェクトが更新されて新しいメモリ位置を参照し、古いメモリが解放されてガベージコレクションに使用されます。この間、書き込みが増えると大きな問題が発生する可能性があります。同時に2回の書き込みがこのメモリコピーの2つのインスタンスをトリガする可能性があります。ロジックを辿ると、参照を更新する最後のスレッドだけが既存のアイテムについて知り、追加しようとしている他のアイテムではないので、要素が失われることがわかります。

ICollection provides a member to synchronize accessこの参照は、拡大/縮小操作で有効なままです。

+0

とinsert( "Key1"、 "Hello")の辞書を宣言してディレクトリ["Key1"] = "これは本当に長い文字列です"を実行すると面白いことです。値のためにメモリを再割り当てしなければならないか、または値がオブジェクトでありオブジェクトが既に割り当てられているので、そのメモリを再利用できるでしょうか?私は多くの辞書オブジェクトの内臓に依存すると思います。 – Joshua

+0

Dictionary の場合、基になるストレージはICollection >です。すでに値を挿入しているということは、ICollectionにレコードが追加されたことを意味します(内容を拡張し、必要に応じてメモリを増やします)。内容は、2つの文字列オブジェクトへの参照を含む単なる構造体です。辞書の値の文字列を他のものに変更すると、メモリ内に新しい文字列が作成され、KeyValuePair参照が更新されます。 IDictionaryの基本ストレージサイズに変更はありません。 –

+0

また、同じキーを2回挿入しようとすると、Insertは例外をスローします。これは、ロックが適切に実装されていないマルチスレッド環境で簡単に発生します。より安全にしたい場合は、存在しないキーにアクセスするだけでキーが作成されます。存在する場合は更新されます。例外をスローしません。同じキーを追加することが実際のエラー条件である場合は、Insertを使用してキャッチします。 directory.Insert( "Key1"、 "Hello");ディレクトリ["Key1"] = "Hello"と対比。 –

0

読み取り操作は、Dictionaryからキーまたは値を取得するものです。書き込み操作は、キーまたは値を更新または追加するものです。したがって、キーを更新するプロセスはライターとみなされます。

スレッドセーフ辞書を作るための簡単な方法は、単純にミューテックスをロックIDictionaryの独自の実装を作成することで、その後の実装にコールを転送する:あなたがリーダーでミューテックスを置き換えることができ

public class MyThreadSafeDictionary<T, J> : IDictionary<T, J> 
{ 
     private object mutex = new object(); 
     private IDictionary<T, J> impl; 

     public MyThreadSafeDictionary(IDictionary<T, J> impl) 
     { 
      this.impl = impl; 
     } 

     public void Add(T key, J value) 
     { 
     lock(mutex) { 
      impl.Add(key, value); 
     } 
     } 

     // implement the other methods as for Add 
} 

いくつかのスレッドで辞書を読み込むだけの場合は、-writer lockを使用します。

Dictionaryオブジェクトはキーの変更をサポートしていません。既存のキーと値のペアを削除し、更新されたキーで新しいペアを追加するだけです。

+0

ConcurrentDictionaryを使うのではなく、これを行う理由は何ですか? – mcmillab

3

まだ言及されていない主なポイントは、TValueはクラス型である場合、Dictionary<TKey,TValue>で開催された事がTValueオブジェクトのアイデンティティになるということです。辞書から参照を受け取った場合、辞書はそれによって参照されたオブジェクトで何ができるかを知らないし気にもしません。辞書に関連付けられたすべてのキーは、それを使用する必要があるコードの前に知られているであろう場合に

一つの有用な小さなユーティリティクラスは次のとおりです。

class MutableValueHolder<T> 
{ 
    public T Value; 
} 

1は、マルチスレッド・コードを持っているしたい場合たくさんのファイルにさまざまな文字列が何回現れているかを数え、あらかじめ文字列をすべて知っていれば、その目的のためにDictionary<string, MutableValueHolder<int>>のようなものを使うことができます。辞書にすべての適切な文字列と各インスタンスのMutableValueHolder<int>インスタンスがロードされると、任意の数のスレッドがMutableValueHolder<int>オブジェクトへの参照を取得し、Threading.Interlocked.Incrementまたは他の方法を使用して、それぞれに関連付けられたValueを変更することができますまったく辞書に。

関連する問題