2011-03-18 52 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