2011-03-18 108 views
6

我一直在阅读新的并发集合,特别是ConcurrentBag引起了我的注意。由于ConcurrentBag内部在每个单独的线程中持有一个本地集以使用它来跟踪这些项,这意味着当线程本身超出范围时,它仍将由ConcurrentBag在内存中引用。这又意味着线程声明的内存以及本地资源? (请原谅我不知道.NET线程对象的确切内部工作原理)ConcurrentBag中可能的内存泄漏?

我可以假设一个用例,其中有1个全局ConcurrentBack用于多线程Web服务,其中有很多客户端添加任务。这些任务由线程池上的线程添加。现在线程池是管理线程的一种非常有效的方式,但它可以根据工作量删除和创建线程。因此,这种网络服务有时会遇到麻烦,因为底层包仍然引用许多应该被销毁的线程。

我创建了一个快速的应用程序来测试此行为:

static ConcurrentBag<int> bag = new ConcurrentBag<int>(); 
    static void FillBag() { for (int i = 0; i < 100; i++) { bag.Add(i); } } 
    static void PrintState() { Console.WriteLine("Bag size is: {0}", bag.Count); } 
    static void Main(string[] args) 
    { 
     var remote = new Thread(x => 
     { 
      FillBag(); 
      PrintState(); 
     }); 
     // empty bag 
     PrintState(); 
     // first 100 items are added on main thread 
     FillBag(); 
     PrintState(); 
     // second 100 items are added on remote thread 
     remote.Start(); 
     remote.Join(); 
     // since the remote thread is gone out of scope, what happened to its local storage which is part of the bag? 
     PrintState(); 
     // now force a cleanup 
     WeakReference weakRemoteReference = new WeakReference(remote); 
     remote = null; 
     GC.Collect(); 
     GC.WaitForPendingFinalizers(); 
     // Now check if the thread still exists 
     if (weakRemoteReference.IsAlive) 
      Console.WriteLine("Remote thread still exists"); 
     PrintState(); 
     Console.ReadLine(); 

和输出印证了我的故事:

Bag size is: 0 
Bag size is: 100 
Bag size is: 200 
Bag size is: 200 
Remote thread still exists 
Bag size is: 200 

这种行为是可以预料的,我是不是犯了一个错误在我的测试或者这可以被认为是一个设计缺陷?

+0

请注意,我命名远程线程已超出范围,当然它仍然在范围内,应该说:“由于远程线程已完成” – Polity 2011-03-18 14:11:12

回答

8

ConcurrentBag的确将事物保留在线程本地存储中,并且如果您放弃线程,则可能会在处导致内存泄漏。但是,该实现能够从一个线程的列表中“窃取”项目以提供给另一个线程。你可以看到在行动这一点,如果你写了以下内容:

ConcurrentBag<int> MyBag = new ConcurrentBag<int>(); 

void DoIt() 
{ 
    for (int i = 0; i < 10; ++i) 
    { 
     MyBag.Add(i); 
    } 

    ThreadPool.QueueUserWorkItem(EmptyBag); 

    Console.Write("Press Enter:"); 
    Console.ReadLine(); 

    Console.WriteLine("{0} items in bag", MyBag.Count); 
} 

void EmptyBag(object state) 
{ 
    int take; 
    while (MyBag.TryTake(out take)) 
    { 
     Console.WriteLine(take); 
    } 
    Console.WriteLine("Bag is empty"); 
} 

如果你运行该程序,并等到“袋为空”消息,按下回车键之前,你会看到袋子确实清空。

所以,只要有一条线从包里读取,最终就会被清空。即使所有项目都是由其他线程添加的。

所以,是的,有可能的内存泄漏。但实际上,如果多个线程正在访问该包,则可能不是问题。

+0

我猜这里有两个线程,QueueUserWorkItem产生一个新的线程。哪个线程正在泄漏(来源或被盗副本)?我怎样才能中断这个过程并彻底关闭?我在任务中使用取消标记... – LamonteCristo 2012-05-20 17:41:42

+0

@ makerofthings7:在此示例代码中,主线程拥有该包,而由“QueueUserWorkItem”产生的线程清空该包。这个例子中没有任何泄漏。一般来说,如果您将东西添加到包中并且不将它们取出,将会出现泄漏。如果您需要更多信息,您可以考虑发布问题。 – 2012-05-20 22:13:06