ConcurrentBag可能出现内存泄漏?

时间:2011-03-18 14:09:48

标签: .net concurrency

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

我可以假设一个用例,你有一个全局ConcurrentBack用于多线程webservice,你有很多客户端添加任务。这些任务由线程池上的线程添加。现在,线程池是一种非常有效的管理线程的方法,但它确实根据工作量删除并创建了线程。因此,这样的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

这种行为是否可以预期,我在测试中是否犯了错误,或者这可能被视为设计缺陷?

1 个答案:

答案 0 :(得分: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");
}

如果您运行该程序并等到“Bag is empty”消息,然后按Enter键,您将看到该包确实已清空。

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

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