62

スレッドセーフであることを確認する必要がある Queue オブジェクトがあります。次のようなロック オブジェクトを使用する方がよいでしょうか。

lock(myLockObject)
{
//do stuff with the queue
}

または、次のように Queue.Synchronized を使用することをお勧めします:

Queue.Synchronized(myQueue).whatever_i_want_to_do();

MSDNのドキュメントを読むと、Queue.Synchronizedを使用してスレッドセーフにする必要があると書かれていますが、ロックオブジェクトを使用した例が示されています。MSDN の記事から:

Queue のスレッド セーフを保証するには、すべての操作をこのラッパーのみで行う必要があります。

コレクションの列挙は、本質的にスレッドセーフな手順ではありません。コレクションが同期されている場合でも、他のスレッドは引き続きコレクションを変更できるため、列挙子は例外をスローします。列挙中のスレッド セーフを保証するには、列挙全体でコレクションをロックするか、他のスレッドによる変更に起因する例外をキャッチします。

Synchronized() を呼び出してもスレッドセーフが保証されない場合、そのポイントは何ですか? ここで何か不足していますか?

4

5 に答える 5

50

個人的には常にロックを好みます。つまり、粒度を決定できるということです。Synchronized ラッパーのみに依存している場合、個々の操作は同期されますが、複数のことを行う必要がある場合 (コレクション全体の反復処理など) は、いずれにせよロックする必要があります。簡単にするために、覚えておくべきことは 1 つだけにすることを好みます。それは、適切にロックすることです。

編集:コメントで述べたように、より高いレベルの抽象化を使用できる場合、それは素晴らしいことです。また、ロックを使用する場合、注意してください。どこでロックされると予想されるかを文書化し、できるだけ短い期間でロックを取得/解放します (パフォーマンスよりも正確さのため)。ロックを保持しているときに不明なコードを呼び出すことを避け、ネストされたロックなどを避けます。

.NET 4 では、高レベルの抽象化 (ロックフリー コードを含む) のサポートが大幅に強化されています。いずれにしても、同期ラッパーの使用はお勧めしません。

于 2008-12-03T21:25:12.517 に答える
35

Synchronized古いコレクション ライブラリのメソッドには、(作業単位ではなくメソッドごとに) 同期の粒度が低すぎるという大きな問題があります。

以下に示すように、同期されたキューには古典的な競合状態があり、Countデキューしても安全かどうかを確認しますが、Dequeueメソッドはキューが空であることを示す例外をスローします。これは、個々の操作がスレッド セーフであるために発生しますが、 の値はCountクエリを実行するときと使用するときとで変化する可能性があります。

object item;
if (queue.Count > 0)
{
    // at this point another thread dequeues the last item, and then
    // the next line will throw an InvalidOperationException...
    item = queue.Dequeue();
}

次のように、作業単位全体を手動でロックする (つまり、カウントをチェックして項目デキューする) ことで、これを安全に書き込むことができます。

object item;
lock (queue)
{
    if (queue.Count > 0)
    {
        item = queue.Dequeue();
    }
}

したがって、同期されたキューから安全に何かをデキューすることはできないので、私はそれを気にせず、手動ロックを使用します。

.NET 4.0 には、適切に実装されたスレッド セーフなコレクションが多数含まれているはずですが、残念ながらそれにはまだ 1 年近くかかります。

于 2008-12-03T21:49:54.730 に答える
16

「スレッド セーフなコレクション」に対する要求と、アトミックな方法でコレクションに対して複数の操作を実行するという要件との間には、しばしば緊張関係があります。

そのため、Synchronized() は、複数のスレッドがアイテムを同時に追加しても壊れないコレクションを提供しますが、列挙中に他の誰もそれに触れてはならないことを知っているコレクションを魔法のように提供するわけではありません。

列挙だけでなく、「このアイテムは既にキューに入っていますか? いいえ、追加します」などの一般的な操作にも、キューだけでなくより広い同期が必要です。

于 2008-12-03T21:19:41.217 に答える
7

このようにして、キューが空であることを確認するためだけにキューをロックする必要はありません。

object item;
if (queue.Count > 0)
{
  lock (queue)
  {
    if (queue.Count > 0)
    {
       item = queue.Dequeue();
    }
  }
}
于 2008-12-03T22:29:24.900 に答える
1

lock(...) {...} ロックを使用することが正しい答えであることは明らかです。

Queue のスレッド セーフを保証するには、すべての操作をこのラッパーのみで行う必要があります。

他のスレッドが .Synchronized() を使用せずにキューにアクセスすると、すべてのキュー アクセスがロックされない限り、クリークになります。

于 2008-12-03T21:17:14.107 に答える