web-dev-qa-db-ja.com

ConcurrentBagの正しい使い方は何ですか?

ConcurrentBagに関する以前の質問はすでに読んでいますが、マルチスレッドでの実装の実際のサンプルは見つかりませんでした。

ConcurrentBagは、スレッドセーフバッグの実装であり、同じスレッドがバッグに格納されたデータを生成および消費するシナリオに最適化されています。」

現在、これは私のコードでの現在の使用法です(実際のコードではなく簡略化されています):

private void MyMethod()
{
    List<Product> products = GetAllProducts(); // Get list of products
    ConcurrentBag<Product> myBag = new ConcurrentBag<Product>();

    //products were simply added here in the ConcurrentBag to simplify the code
    //actual code process each product before adding in the bag
    Parallel.ForEach(
                products,
                new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount },
                product => myBag.Add(product));

    ProcessBag(myBag); // method to process each items in the concurrentbag
}

私の質問:
これはConcurrentBagの正しい使い方ですか?このようなシナリオでConcurrentBagを使用しても大丈夫ですか?

私にとっては、単純なList<Product>と手動ロックの方がうまくいきます。これは、上記のシナリオが既に「同じスレッドがバッグに保存されたデータを生成および消費する」ルールを破っているためです。
また、並列の各スレッドで作成されたThreadLocalストレージは、操作後も(スレッドが再利用されたとしてもこのままですか?)存在し、望ましくないメモリリークを引き起こす可能性があることもわかりました。
私はこの人たちの中にいますか?または、ConcurrentBagの項目を削除する単純なクリアまたは空のメソッドで十分ですか?

48
hisoka21

これは、ConcurrentBagの適切な使用のように見えます。スレッドのローカル変数はバッグのメンバーであり、バッグと同時にガベージコレクションの対象になります(内容をクリアしても解放されません)。あなたの場合は、ロック付きの単純なリストで十分です。ループで行っている作業がまったく重要な場合、スレッド同期のタイプは全体的なパフォーマンスにそれほど影響しません。その場合、使い慣れたものをより快適に使用できます。

もう1つのオプションは、 ParallelEnumerable.Select を使用することです。これは、より厳密に実行しようとしていることに一致します。繰り返しになりますが、パフォーマンスの違いはごくわずかであり、あなたが知っていることに固執することは何の問題もありません。

いつものように、これのパフォーマンスが重要な場合、試して測定することに代わるものはありません。

22
bmm6o

私にはbmm6oが正しくないようです。 ConcurrentBagインスタンスには、アイテムを追加するスレッドごとにミニバッグが内部に含まれているため、アイテムの挿入にはスレッドロックが含まれないため、_Environment.ProcessorCount_スレッドはすべて待機せずにフルスイングする可能性がありますスレッドコンテキストスイッチなし。収集されたアイテムを反復処理する場合、スレッドの同期化が必要になる場合がありますが、元の例では、すべての挿入が行われた後、単一のスレッドで反復処理が行われます。さらに、ConcurrentBagがスレッド同期の最初のレイヤーとしてインターロック手法を使用している場合、Monitor操作をまったく使用しないことも可能です。

一方、通常の_List<T>_インスタンスを使用して、各Add()メソッド呼び出しをlockキーワードでラップすると、パフォーマンスが大幅に低下します。まず、定数Monitor.Enter()およびMonitor.Exit()呼び出しにより、それぞれがカーネルモードに深く入り込み、Windows同期プリミティブを操作する必要があります。 2番目に、2番目のスレッドがまだ追加を完了していないために、1つのスレッドが2番目のスレッドによってブロックされることがあります。

私に関しては、上記のコードはConcurrentBagクラスの正しい使用法の非常に良い例です。

5
Sergey Volodko

_List<T>_がAdd()メソッドをロックして使用されている場合、スレッドを待機させ、Parallel.ForEach()を使用することによるパフォーマンスの向上を低減します。

0
Adeel Meer