4

インベントリを表す配列があり、50 近くの要素 (アイテム: いくつかのコスト オブジェクト) があり、それにはリーダー書き込みロックが必要です (単純なロックでも十分だと思います)。参照の変更と値の変更の両方をサポートする必要があります。

配列の異なる位置への読み取りと書き込みはスレッドセーフであるため ( Proof )、同じ配列位置での複数の読み取り/書き込み操作もスレッドセーフであることを確認したいと思います。

確かに 50 個のリーダーライターロックを作成できますが、それはしたくありません ;) これをアーカイブする方法はありますか? (ConcurrentList/Dictionary/etc. は知っていますが、配列が必要です...)

ありがとう

4

2 に答える 2

5

配列内の参照を置き換える場合、参照スワップは本質的にアトミックであるため、これはすでに安全です。したがって、次を使用できます。

var val = arr[index];
// now work with val

var newVal = ...
arr[index] = newVal;

少なくとも引き裂かれた参照を回避するという点では、完全に安全です。したがって、実用的なオプションの 1 つは、オブジェクトを immutableにして、上記をそのまま使用することです。値を変更する必要がある場合は、ローカル コピーを取得し、それに基づいて新しいバージョンを作成してから、それらを交換します。更新が失われることが問題である場合はInterlocked.CompareExchange、再適用ループをうまく使用できます (つまり、「勝つ」まで変更を再適用し続けます)。これにより、ロックの必要がなくなります。

ただし、個々のオブジェクトを変更する場合は、ゲームが変わります。オブジェクトを内部的にスレッドセーフにすることもできますが、これは通常きれいではありません。すべてのオブジェクトに対して 1 つのロックを設定できます。ただし、きめ細かなロックが必要な場合は、複数のロックが必要になります。

私のアドバイス: オブジェクトを不変にし、アトミック参照スワップ トリックを使用するだけです。

于 2012-08-14T10:57:41.307 に答える
3

まず、ロックは必要ないかもしれません。CPU が各読み取りと書き込みをアトミックに処理する型の配列を使用した読み取りと書き込みは、それ自体がスレッド セーフです (ただし、古い読み取りを避けるためにメモリ バリアを配置することをお勧めします)。

x = 34とはいえ、整数はスレッドセーフですがそうではないのと同じようにx++、現在の値に依存する書き込み (したがって読み取りと書き込み) がある場合、それはスレッドセーフではありません。

ロックは必要だが 50 個ほどにしたくない場合は、ストライプ化できます。ReaderWriterSlim最初にストライプ化されたロックを設定します (小さなサンプル コードではなく単純なロックを使用します。同じ原則が適用されます)。

var lockArray = new object[8];
for(var i =0; i != lockArray.Length; ++i)
  lockArray[i] = new object();

次に、それを使用するとき:

lock(lockArray[idx % 8])
{
  //operate on item idx of your array here
}

これは、すべてに対して 1 つのロックのシンプルさとサイズと、要素ごとに 1 つのロックのメモリ使用量とのバランスです。

ある要素の操作が別の要素の操作に依存している場合、配列のサイズを変更する必要がある場合、または複数のロックが必要なその他の場合は、大きな問題が発生します。多くのデッドロック状況は、常に同じ順序でロックを取得することで回避できます (そのため、複数のロックを必要とする他のスレッドが、必要なロックを保持している間に、既に持っているロックを取得しようとすることはありません)。これらのケース。

また、たとえばインデックス 3 とインデックス 11 を扱っている場合は、オブジェクト 3 を 2 回ロックしないようにする必要があります (この特定の再帰的ロックがうまくいかない方法は考えられませんが、単に回避しない理由はありません)。再帰ロックが安全なケースの 1 つであることを証明する必要はありませんか?)

于 2012-08-14T10:57:14.867 に答える