“抢占GC禁用”模式下的.NET线程阻塞GC并可能导致死锁

时间:2015-01-20 03:20:50

标签: .net multithreading performance garbage-collection clr

[Edit0]人们为优化实例创建(通过使用数组缓冲区)提供了一些好的建议,从而降低了GC周期,但也请解释为什么只有这个线程被标记为'先发制人GC禁用',我的情况是僵局吗?

最近我正在对我们在.NET 3.5中编写的应用程序(在Windows XP上)进行一些测试,我注意到在某些情况下应用程序性能非常糟糕,TaskManager中显示的内存比正常情况多,但不挂。我做了一个内存转储,只需输入Microsoft DebugDiag tool,对于不太擅长WinDbg的人来说,这是一个很好的工具。

我知道应用程序中一定存在内存泄漏问题,但这里的问题是关于GC。

生成的报告中有一行:

  

线程6 触发了垃圾回收。垃圾收集器线程不会开始做它的工作,直到有线程的时候   先发制人GC禁用已完成执行。以下主题禁用了先发制人GC:主题6

从描述中,我将其理解为GC中的死锁和“先发制人GC禁用”主题,(但为什么不挂?)然后我很奇怪为什么那个帖子被标记为'先发制人GC禁用'。

然后我检查了 Thread 6 的调用堆栈,找出了源代码,这是一个明确创建的线程:

//Create the thread.
thread = new Thread(Execute) { Priority = ThreadPriority.Lowest };
thread.Start();

这个线程做的是线程安全地从队列中取消日志文本行的队列,并刷新到磁盘文件:

// Wait for an event from the thread that en-queue
waitEvent.WaitOne();  
if (!disposed)
{
    // Clear the entry queue.  
    bool queueEmpty;
    do
    { 
        // Pick the next logEntry in the queue safely in a locked way.
        LogEntry logEntry = null;
        lock (entryQueueLock)
        {
            logEntry = entryQueue.Dequeue();
        }

        var text = logEntry.Text;
        byte[] textBytes = Encoding.Default.GetBytes(text);
        fileStream.Write(textBytes, 0, textBytes.Length);
        fileStream.Flush();            
     }
     while (!queueEmpty);
}

线程6的调用堆栈在此行停止,表示该行启动了GC:

byte[] textBytes = Encoding.Default.GetBytes(text);

然后请帮助解释为什么只有线程6(在近50个线程中)有'先发制人GC禁用',以及如何避免这种情况: enter image description here

1 个答案:

答案 0 :(得分:1)

这不是您的具体问题,但您可以通过this advice concerning the Encoding class来显着减少线程生成的垃圾量:

  

如果您的应用必须转换大量数据,则应重用输出缓冲区。在这种情况下,支持字节数组的GetBytes版本是最佳选择。

你可以轻松地做到这一点,如下:

/* helper extension method */
public static void Grow(ref byte[] buffer, int minSize)
{   if (minSize > buffer.Length) buffer = new byte[minSize];  }

/* outside the loop */
byte conversionBuffer[] = new byte[2048];
Encoding fileEncoding = Encoding.Default;

/* replace your GetBytes call with */
Grow(ref conversionBuffer, fileEncoding.GetMaxBytes(text));
int bytesUsed = fileEncoding.Get(text, 0, text.Count, conversionBuffer, 0);

/* and your Write call with */
fileStream.Write(conversionBuffer, 0, bytesUsed);

另外,我建议您使用ConcurrentQueue而不是手动锁定。