14

重複
の可能性:ConcurrentBagでのメモリリークの可能性?

編集1:

実際の質問はです。これを確認できますか、それとも私のサンプルが間違っていて、明らかな何かが欠けていますか?

ConcurrentBagは、順序付けされていないリストの単純な代替品だと思いました。しかし、私は間違っていました。ConcurrentBagは、作成中のスレッドにThreadLocalとして自分自身を追加しますが、これは基本的にメモリリークを引き起こします。

   class Program
    {
        static void Main(string[] args)
        {
            var start = GC.GetTotalMemory(true);
            new Program().Start(args);
            Console.WriteLine("Diff: {0:N0} bytes", GC.GetTotalMemory(true) - start);
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
            Thread.Sleep(5000);
        }

        private void Start(string[] args)
        {
            for (int i = 0; i < 1000; i++)
            { 
                var bag = new ConcurrentBag<byte>();
                bag.Add(1);
                byte by;
                while (bag.TryTake(out by)) ;
            }
        }

バッグに追加するデータの量に応じて、Diffを250KBまたは100GBにすることができます。データもバッグもなくなります。

Windbgでこれに侵入し、!DumpHeapタイプの並行処理を実行すると

...。

000007ff00046858        1           24 System.Threading.ThreadLocal`1+GenericHolder`3[[System.Collections.Concurrent.ConcurrentBag`1+ThreadLocalList[[System.Byte, mscorlib]], System],[System.Threading.ThreadLocal`1+C0[[System.Collections.Concurrent.ConcurrentBag`1+ThreadLocalList[[System.Byte, mscorlib]], System]], mscorlib],[System.Threading.ThreadLocal`1+C0[[System.Collections.Concurrent.ConcurrentBag`1+ThreadLocalList[[System.Byte, mscorlib]], System]], mscorlib],[System.Threading.ThreadLocal`1+C0[[System.Collections.Concurrent.ConcurrentBag`1+ThreadLocalList[[System.Byte, mscorlib]], System]], mscorlib]]
000007feed812648        2           64 System.Collections.Concurrent.ConcurrentStack`1[[System.Int32, mscorlib]]
000007feece41528        1          112 System.Collections.Concurrent.CDSCollectionETWBCLProvider
000007ff000469e0     1000        32000 System.Threading.ThreadLocal`1+Boxed[[System.Collections.Concurrent.ConcurrentBag`1+ThreadLocalList[[System.Byte, mscorlib]], System]]
000007feed815900     1000        32000 System.Collections.Concurrent.ConcurrentStack`1+Node[[System.Int32, mscorlib]]
000007ff00045530     1000        72000 System.Collections.Concurrent.ConcurrentBag`1+ThreadLocalList[[System.Byte, mscorlib]]

空のConcurrentBagを作成して、一部のワーカースレッドにデータを追加させると、ConcurrentBagとそのデータは、作成中のスレッドがまだ生きている限りそこにあります。

このようにして、数GBのメモリリークが発生しました。リストとロックを使用してこれを「修正」しました。ConcurrentBagは高速かもしれませんが、同じオブジェクトの有効期間を持つリストの単純な置き換えとしては役に立ちません。

メインスレッドでConcurrentBagを作成した場合、スレッドが存続している限り、それを保持します。これは私が期待することではなく、大きな痛みを引き起こす可能性があります。

4

3 に答える 3

3

ConcurrentBag が ThreadLocal コピーを作成するのは正しいことです。実際、それらは同じスレッドがデータをバッグに読み書きするシナリオ向けに最適化されています: "... ConcurrentBag はスレッドセーフなバッグ実装であり、同じスレッドは、バッグに格納されたデータの生成と消費の両方を行います。」

一方、ここでは奇妙な動作は見られません。スレッドは存続し、同時にバッグも存続します。スレッドが終了すると、GC がその仕事を行います。

于 2012-06-01T12:58:38.563 に答える
2

ドキュメントから

ConcurrentBag はスレッド セーフなバッグ実装であり、同じスレッドがバッグに格納されたデータの生成と消費の両方を行うシナリオ向けに最適化されています。

スレッドセーフなコレクションをいつ使用するか

プロデューサーとコンシューマーが混在するシナリオでは、大規模なワークロードと小規模なワークロードの両方で、ConcurrentBag は通常、他のどの同時コレクション タイプよりもはるかに高速でスケーラブルです。

ConcurrentBag に関するあなたの仮定は間違っていると思います。まず、スレッドローカルストレージを使用して、アクセスするスレッドごとに個別の内部リストを提供します。これは単なるスレッドセーフな順序なしリストではありません。

バッグが TLS を使用していることがわかれば、メモリ リークと見なされるのは実際には予期される動作です。スレッドが使用されている限り、データを消去する必要はありません。

そうは言っても、私は今まで ConcurrentBag の追加機能に気付いていませんでした。

「 ConcurrentBag とは」で、ConcurrentBag がどのように個別のリストを使用するか、およびさまざまなシナリオでそのメソッドのコストを使用する方法についての非常に適切な説明を見つけました。この説明が MSDN ドキュメントに掲載されることを願っています。

個人的には、ConcurrentBag の特別な動作がわかったので、ConcurrentBag をもっと使い始めるつもりです。

アップデート:

Ayende によるこの投稿を確認したところ、 「ConcurrentBag が使用する ThreadLocal は、多くのインスタンスを持つことを期待していませんでした。これは修正され、かなり高速に実行できるようになりました」と述べています。

于 2012-06-01T13:04:48.660 に答える
-3

2番目のGC.Collect()の後にConsole.WriteLineを移動してみませんか?そうしないと、意図したよりも多くのオブジェクトを見ている可能性があります。

メインのすべてをループに入れて、いくつかの統計を取得することもできます。書き込みを移動しない場合でも、後で小さなデルタが表示される可能性があります。

乾杯!

于 2012-08-14T03:55:17.427 に答える