EDIT1:
实际问题是。你能证实这一点,还是我的样本错了,我错过了一些明显的东西?
我认为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 250 KB或100 GB,具体取决于我添加到行李箱的数据量。数据和袋子都消失了。
当我用Windbg打破这个时,我做了一个 !DumpHeap -type Concurrent
...
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的内存泄漏。我通过使用List和锁来“修复”这个问题。 ConcurrentBag可能很快,但它对于具有相同对象生存期的List的简单替换是无用的。
如果我在主线程上创建了一个ConcurrentBag,只要线程处于活动状态,我就会保留它。这不是我所期望的,它可能会导致重大痛苦。
答案 0 :(得分:3)
ConcurrentBag创建一个ThreadLocal副本是对的,实际上它们针对同一个线程正在读取数据并将数据写入包的场景进行了优化:“... ConcurrentBag是一个线程安全的包实现,针对场景进行了优化同一个线程将产生和消耗存储在包中的数据。“
另一方面,我在这里看不到奇怪的行为;线程生活和并发包生活。当线程完成时,GC将完成它的工作。
答案 1 :(得分:1)
来自文档
ConcurrentBag是一个线程安全的包实现,针对同一个线程生成和消费存储在包中的数据的场景进行了优化。
和When to use a thread-safe collection
在混合的生产者 - 消费者场景中,对于大型和小型工作负载,ConcurrentBag通常比任何其他并发集合类型更快,更具可伸缩性。
我会说你对ConcurrentBag的假设是不正确的。首先,它不会将itsels添加到ThreadLocal,它使用线程本地存储为访问它的每个线程提供单独的内部列表。它不仅仅是一个线程安全的无序列表。
一旦你意识到包使用TLS,你认为内存泄漏实际上是预期的行为 - 只要线程正在使用就没有必要清除数据。
说了这么多,直到现在我还没有意识到ConcurrentBag的额外功能。
我已经很好地描述了ConcurrentBag如何使用单独的列表以及“What is ConcurrentBag”中不同场景中的方法成本。我希望这个描述出现在MSDN文档中。
就个人而言,我现在开始使用ConcurrentBag,因为我知道它的特殊行为。
<强>更新强>
刚刚检查了Ayende的this post说“ThreadLocal,ConcurrentBag使用的,没想到会有很多实例。这已经修复了,现在可以运行得相当快”
答案 2 :(得分:-3)
为什么不在第二个GC.Collect()之后移动Console.WriteLine?否则你可能会看到比你想象的更多的物体。
您还可以尝试将Main中的所有内容放入循环中以获取一些统计信息。即使你不动你的写作,你也可能会看到更小的增量。
干杯!