.NET 线程处于“禁用抢先 GC”模式,这会阻塞 GC 并可能导致死锁

Sha*_*awn 6 .net clr performance multithreading garbage-collection

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

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

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

生成的报告中有一行:

线程 6触发了垃圾回收。垃圾收集器线程直到禁用了抢占式 GC的线程完成执行后才会开始工作 。以下线程禁用了抢先式 GC:线程 6

从描述中,我将其理解为GC的死锁和“禁用抢先 GC ”线程,(但为什么不挂起??)然后我想知道为什么该线程标记为“禁用抢先 GC ”。

然后我检查了Thread 6的调用堆栈并找出源代码,它是一个由以下人员显式创建的线程:

//Create the thread.
thread = new Thread(Execute) { Priority = ThreadPriority.Lowest };
thread.Start();
Run Code Online (Sandbox Code Playgroud)

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

// 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);
}
Run Code Online (Sandbox Code Playgroud)

线程 6 的调用栈停在这一行,意味着这一行开始了 GC:

byte[] textBytes = Encoding.Default.GetBytes(text);
Run Code Online (Sandbox Code Playgroud)

那么请帮助解释为什么只有线程 6(在近 50 个线程中)有这个“先发制人的 GC 禁用”,以及如何避免这种情况: 在此处输入图片说明

Ben*_*igt 1

这不是您的具体问题,但您可以通过遵循有关该类的建议Encoding来显着减少线程生成的垃圾量:

如果您的应用程序必须转换大量数据,则应重用输出缓冲区。在这种情况下,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);
Run Code Online (Sandbox Code Playgroud)

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