线程内存泄漏

问题描述:

我试图在产生多个线程的大型C#程序中查找内存泄漏.在此过程中,我创建了一个小的辅助程序,用于测试一些基本的东西,并且发现了一些我确实不理解的行为.

I am trying to track down a memory leak in a larger C# program which spawns multiple threads. In the process, I have created a small side program which I am using to test some basic things, and I found some behavior that I really do not understand.

class Program
{
    static void test()
    {
    }

    static void Main(string[] args)
    {
        while (true)
        {              
            Thread test_thread = new Thread(() => test());
            test_thread.Start();
            Thread.Sleep(20);
        }
    }
}

运行该程序,我发现该程序的内存使用量一直稳定增长,而没有停止.短短几分钟内,内存使用量就超过了100MB,并且还在不断攀升.如果我注释掉test_thread.Start();行,则该程序使用的内存最大约为几兆字节,并且逐渐减少.我还尝试使用GC.Collect()在while循环结束时强制进行垃圾回收,但是它似乎没有任何作用.

Running this program, I see that the memory usage of the program increases steadily without stopping. In just a few minutes the memory usage goes well over 100MB and keeps climbing. If I comment out the line test_thread.Start();, the memory used by the program maxes out at about a few megabytes, and levels out. I also tried forcing garbage collection at the end of the while loop using GC.Collect(), but it did not seem to do anything.

我认为该函数将在执行完成后立即取消引用线程,从而允许GC清除它,但这似乎没有发生.我一定不能在这里更深入地了解某些内容,并且希望能在解决此泄漏方面提供一些帮助.预先感谢!

I thought that the thread would be dereferenced as soon as the function is finished executing allowing the GC to mop it up, but this doesn't seem to be happening. I must not be understanding something deeper here, and I would appreciate some help with fixing this leak. Thanks in advance!

这是设计使然,您的测试程序应该表现出失控的内存使用率.您可以从Taskmgr.exe查看潜在原因.使用查看+选择列,然后勾选句柄".观察过程的句柄数量是如何稳定增加的.内存使用量随之增加,反映了句柄对象使用的非托管内存.

This is by design, your test program is supposed to exhibit runaway memory usage. You can see the underlying reason from Taskmgr.exe. Use View + Select Columns and tick "Handles". Observe how the number of handles for your process is steadily increasing. Memory usage goes up along with that, reflecting the unmanaged memory used by the handle objects.

设计选择是非常勇敢的选择,CLR每个线程使用5个操作系统对象.管道,用于同步.这些对象本身是一次性的,设计选择是 not 使得Thread类实现IDisposable.对于.NET程序员而言,这将是一个很大的困难,很难在正确的时间进行Dispose()调用.没在Task类设计中表现出的勇气,反而引起了很多麻烦,并且

The design choice was a very courageous one, the CLR uses 5 operating system objects per thread. Plumbing, used for synchronization. These objects are themselves disposable, the design choice was to not make the Thread class implement IDisposable. That would be quite a hardship on .NET programmers, very difficult to make the Dispose() call at the right time. Courage that wasn't exhibited in the Task class design btw, causing lots of hand-wringing and the general advice not to bother.

在精心设计的.NET程序中,这通常不是 问题. GC经常运行的地方足以清理那些OS对象.线程对象很少创建,使用ThreadPool处理运行时间很短的线程(例如测试程序使用的线程).

This is not normally a problem in a well-designed .NET program. Where the GC runs often enough to clean up those OS objects. And Thread objects are creating sparingly, using the ThreadPool for very short running threads like your test program uses.

可能是,我们看不到您的真实程序.不要提防从这样的综合测试中得出太多的结论.您可以通过Perfmon.exe查看GC统计信息,从而使您了解其运行频率是否足够高.不错的.NET内存分析器是首选武器. GC.Collect()是备用武器.例如:

It can be, we can't see your real program. Do beware of drawing too many conclusions from such a synthetic test. You can see GC statistics with Perfmon.exe, gives you an idea if it is running often enough. A decent .NET memory profiler is the weapon of choice. GC.Collect() is the backup weapon. For example:

static void Main(string[] args) {
    int cnt = 0;
    while (true) {
        Thread test_thread = new Thread(() => test());
        test_thread.Start();
        if (++cnt % 256 == 0) GC.Collect();
        Thread.Sleep(20);
    }
}

现在您将看到它来回反弹,从不超过4 MB.

And you'll see it bounce back and forth now, never getting much higher than 4 MB.