web-dev-qa-db-ja.com

C#では、スレッドの安全性のためにQueue.Synchronizedまたはlock()を使用する方が良いでしょうか?

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

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

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

Queue.Synchronized(myQueue).whatever_i_want_to_do();

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

キューのスレッドの安全性を保証するには、すべての操作はこのラッパーのみで実行する必要があります。

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

Synchronized()を呼び出してもスレッドの安全性が保証されない場合、それの目的は何ですか?ここに何かが足りませんか?

58
Jon Tackabury

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

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

.NET 4には、lotより高いレベルの抽象化(ロックフリーコードを含む)のサポートが追加されています。どちらにしても、同期されたラッパーの使用はお勧めしません。

47
Jon Skeet

古いコレクションライブラリの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();
}

次のように、作業単位全体の手動ロックを使用してこれを安全に記述できます(つまり、カウントをチェックしますandデキュー)。

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

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

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

35
Greg Beech

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

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

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

15
Will Dean

この方法では、キューが空だったことを確認するためにキューをロックする必要がありません。

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

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

キューのスレッドの安全性を保証するために、すべての操作はこのラッパーのみで実行する必要があります。

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

1
Greg Hurlman