web-dev-qa-db-ja.com

リストを使用する場合、ReaderWriterLockSlimは(従来のロックと比較して)スレッドの安全性と速度の効率を提供しますか?

すべての更新ループで1回書き込むリストがあります。次に、そのリストを読み取り、そのデータを操作するための複製を作成するいくつかのタスクを生成します。参照で使用するのではなく、新しいリストを作成するためのList.ToList()呼び出しです。各タスクは自身のデータを操作し、その結果を生成します。現在、私は伝統的なロックの形式で使用しています

_lock(LockObject){
//write stuff
}
_

書くとき

_lock(LockObject){
localList=List.ToList();
}
_

読むとき。

現在の方法の代わりにReaderWriterLockSlimオブジェクトを使用すると、速度の点でメリットがありますか?この場合、ToList()メソッドは読み取りと見なされますか?

説明の編集

  1. リーダーはデータを異なる方法で操作するため、コピーを作成します。リーダーはそれぞれ独自の方法でデータをリストから削除または追加します。 (まだ必要な以前のデータを追加するものもあれば、不要なデータを削除するものもあります。また、更新ループは数秒ごとに発生するため、一般的です。
  2. データ入力が本当に速い場合、すべてのリーダーが処理を完了する前に更新/書き込みループが開始する場合があります(ただし、更新されたデータを取得する場合は問題ありません)。
  3. 基本的に、読者は現在のデータを他の人に提示するためにそこにいます。したがって、データが表示される前にデータが変更されても問題ありません。更新中にデータを処理しようとしない限り(おそらく例外がスローされます)
3
John Demetriou

場合によります。読み取り操作が書き込み操作よりも著しく一般的である場合、クリティカルセクションではなくリーダー/ライターロックを使用すると効果的です。しかし、おそらくアプリケーションアーキテクチャを変更することで、さらに多くを達成できると思います。

書き込みがまれな場合は、コレクションを不変として扱い、変更時にまったく新しいリストを作成することで、パフォーマンスが大幅に向上する可能性があります。次に Interlocked.CompareExchange を使用して、スレッドセーフな方法でリストへの参照を更新できます。これにより、読者が防御的なコピーを作成する必要がなくなり(読み取りよりも書き込みの方が一般的である場合、これは大きな利益になるはずです)、ロックする必要がなくなります。

または、書き込みが読み取りよりも一般的である場合は、リストの代わりとしてConcurrentStackを評価する価値があるかもしれません。これは、ロックを必要とせずにコピーを作成するために使用できるアトミックToArrayメソッドを提供します。または、リストで順序が重要でない場合は、ConcurrentBagの方がパフォーマンスが良い場合があります。

明確化後に編集

ロックを完全になくすことができるので、リストを変更するときはリストをコピーする方がよいと私はまだ思います。読者が処理中にリストの内容を変更する必要がある場合でも、ストリーミングスタイルを使用するようにリファクタリングすることをお勧めします。 LINQ to Objects。これらの操作は実際にはデータを複製しません。既存のコードをこのスタイルに変換する方法の詳細については、Martin Fowler ループとコレクションパイプラインを使用したリファクタリング を参照してください。

それに失敗すると、リーダー/ライターのロックに切り替わる小さな利益が見られると思いますが、アプリケーションでロックの競合が異常に問題にならない限り、それは目立ちません。

3
Jules