垃圾收集器无法回收内存资源

时间:2016-07-01 19:39:49

标签: c# multithreading garbage-collection blockingcollection

我正在尝试测试.net BlockingCollection内部如何处理重物。

我使用一些API获取重物,并希望在多个线程中处理它。为了做到这一点,我有一个线程读取重物并将它们推入多个线程,其中每个线程都有自己的阻塞集合,每个线程都将对象从集合和进程中取出。我期待当对象从所有集合中移除时,GC应该能够清理它。清理工作没有发生,我的程序内存不足。

在两者之间调用GC.Collect()正在帮助我完成整个过程,但它有很大的性能损失,我承担不起。

我唯一的问题是为什么即使对象超出范围,垃圾收集器也无法释放资源。

public class DummyProcessor
{
    List<BlockingCollection<object>> listOfBlockingCollection = null;

    void ProcessCollection(object blockingCollection)
    {
        BlockingCollection<object> collection = (BlockingCollection<object>)blockingCollection;

        while (collection.IsCompleted == false)
        {
            object heavyObject = collection.Take();

            CallExternalProcess(heavyObject);
        }
    }

    private void CallExternalProcess(object heavyObject)
    {
        throw new NotImplementedException();
    }

    public void Analyze(object heavyObject)
    {
        if (listOfBlockingCollection == null)
        {
            listOfBlockingCollection = new List<BlockingCollection<object>>();

            for (int i = 0; i < 25; i++)
            {
                BlockingCollection<object> coll = new BlockingCollection<object>();

                listOfBlockingCollection.Add(coll);

                Thread pt = new Thread(new ParameterizedThreadStart(ProcessCollection));

                pt.Start(coll);
            }
        }

        for (int i = 0; i < 25; i++)
        {
            listOfBlockingCollection[i].Add(heavyObject);
        }
    }
}

2 个答案:

答案 0 :(得分:2)

如果以线程安全的方式调用Analyze,我现在无法在此代码中看到任何威胁。如果dotMemory有一些分析,请将其添加到问题中。

但是,我想指出你调用Analyze方法的方式。首先,您以非线程安全的方式初始化listOfBlockingCollection变量,因为如果您有两个运行Analyze方法的线程,则可以使竞争成为一种情况其中多个线程在代码中运行if子句。

在这种情况下,您创建了至少25个鬼线程,更重要的是,您可以了解情况,然后您的listOfBlockingCollection 超过25个项:如果某个线程已经输出if子句,而另一个仍然创建线程并将集合添加到列表中(例如,您可以查看listOfBlockingCollection.Count)。一个线程在内存中至少为2 MB,而作为IDisposable对象,它的收集速度并不是您想要的那么快。

您的代码的其他问题是25线程是一个无效的线程数(只有当您拥有一个具有32个核心的超级计算机时,这可能是一个选项),因为上下文切换。最好使用等于系统内核数量的线程数,或者更好的是,将代码切换到面向Task(您可以轻松地为处理创建25个任务,并在其中循环)。

另一个选择,如果您需要一些额外的数据工作流程,您可以在应用程序中使用25 TPL DataFlowActionBlocks库,启动25个不同的流程。完成重型对象队列后,您可以轻松send the Complete command遍布各个块并完成执行。

答案 1 :(得分:1)

通过dotMemory运行应用程序后,我发现生成的重物太快,以至于它们被累积到计算机内存之外的大量BlockingCollection中。 我对GC.Collect的显式调用并不是因为它正在释放使用过的重对象,而是为重型对象生成添加了一些暂停,为消费者线程提供了更多时间来清除BlockingCollection中已有的内容。
所以我不得不介绍重型对象生产者和消费者之间的等待,这样我就不会过度使用可用内存。为此我使用AutoResetEvent。我在收集大小达到某个范围后调用AutoResetEvent.WaitOne,一旦它低于我调用AutoResetEvent.Set的范围,再次运行生产者。

感谢大家对此的意见。