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)) ;
            }
        }

我可以根据我添加到包中的数据量来制作 250 KB 或 100 GB 的差异。数据和包都消失了。

当我用 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 可能很快,但作为具有相同对象生命周期的 List 的简单替换是没有用的。

如果我在主线程上创建了一个 ConcurrentBag,只要线程还活着,我就会保留它。这不是我所期望的,它可能会导致严重的痛苦。

4

3 回答 3

3

你说得对,ConcurrentBag 创建了一个 ThreadLocal 副本,实际上它们针对同一个线程正在读取数据并将数据写入包的场景进行了优化:“...... ConcurrentBag 是一个线程安全的包实现,针对相同的场景进行了优化线程将同时生产和消费存储在包中的数据。”

另一方面,我在这里没有看到奇怪的行为;线程存在,并发包存在。当线程完成 GC 将完成它的工作。

于 2012-06-01T12:58:38.563 回答
2

从文档

ConcurrentBag 是一个线程安全的包实现,针对同一线程将同时生产和使用包中存储的数据的场景进行了优化。

以及何时使用线程安全集合

在混合的生产者-消费者场景中,对于大型和小型工作负载,ConcurrentBag 通常比任何其他并发收集类型更快且更具可扩展性。

我想说您对 ConcurrentBag 的假设是不正确的。首先,它不会将其自身添加到 ThreadLocal,它使用线程本地存储为访问它的每个线程提供单独的内部列表。它不仅仅是一个线程安全的无序列表。

一旦您意识到包使用 TLS,您认为内存泄漏实际上是预期的行为 - 只要线程在使用中,就不需要清除数据。

说了这么多,我自己直到现在才意识到 ConcurrentBag 的额外功能。

我在“什么是 ConcurrentBag ”中找到了关于 ConcurrentBag 如何使用单独列表及其方法在不同场景中的成本的很好描述。我真希望这个描述出现在 MSDN 文档中。

就个人而言,我将开始更多地使用 ConcurrentBag,因为我知道它的特殊行为。

更新:

刚刚检查了 Ayende 的这篇文章,说“ConcurrentBag 使用的 ThreadLocal 没想到会有很多实例。这个问题已经修复,现在可以运行得相当快”

于 2012-06-01T13:04:48.660 回答
-3

为什么不在第二个 GC.Collect() 之后移动 Console.WriteLine?否则,您可能会看到比您预期更多的对象。

您还可以尝试将 Main 中的所有内容放入循环中以获取一些统计信息。即使您不移动您的写入,您也可能会在之后看到更小的增量。

干杯!

于 2012-08-14T03:55:17.427 回答