ServerGC应用程序中的GC线程计数非常高

时间:2013-09-12 10:08:42

标签: .net multithreading garbage-collection clr windbg

TL; DR:启用了Server GC的应用程序会显示数十个特殊的 GC线程并挂起超时。有什么可以解释的?


这些天我一直处于.NET服务上发生的奇怪的多线程/争用问题上。症状如下:

  • 程序会长时间挂起(几秒钟到几分钟)
  • 线程数异常高
  • 当程序停止响应时存在争用的高峰(参见下图)
  • 相同的程序部署在不同的服务器上,有些实例根本没有问题(相同的硬件/ OS / CLR)

Peak of contention

我立即怀疑我们的代码中存在一个问题,该问题会导致托管线程池随着时间的推移启动大量线程,所有这些都试图共享一个或多个公共资源。看起来我们对ThreadPool的使用非常小且非常有用。

我设法得到一个非挂起服务的转储文件,该服务已经拥有非常多的线程(超过100,当它在正常状态下应该是20左右)时

使用windbg + sos,我们确定ThreadPool大小正常:

0:000> !threadpool
CPU utilization: 0%
Worker Thread: Total: 8 Running: 1 Idle: 7 MaxLimit: 32767 MinLimit: 32
Work Request in Queue: 0
--------------------------------------
Number of Timers: 1
--------------------------------------
Completion Port Thread:Total: 1 Free: 1 MaxFree: 64 CurrentLimit: 1 MaxLimit: 1000 MinLimit: 32

只有8个工作线程......然后我列出了所有托管线程堆栈,发现了很多我无法识别的线程堆栈。请参阅下面的一个示例:

0:000> !eestack
(...)
Thread  94
Current frame: ntdll!NtWaitForSingleObject+0xa
Child-SP         RetAddr          Caller, Callee
0000008e25b2f770 000007f8f5a210ea KERNELBASE!WaitForSingleObjectEx+0x92, calling ntdll!NtWaitForSingleObject
0000008e25b2f810 000007f8ece549bf clr!CLREventBase::WaitEx+0x16c, calling kernel32!WaitForSingleObjectEx
0000008e25b2f820 000007f8f5a2152c KERNELBASE!SetEvent+0xc, calling ntdll!NtSetEvent
0000008e25b2f850 000007f8ece54977 clr!CLREventBase::WaitEx+0x103, calling clr!CLREventBase::WaitEx+0x134
0000008e25b2f8b0 000007f8ece548f8 clr!CLREventBase::WaitEx+0x70, calling clr!CLREventBase::WaitEx+0xe4
0000008e25b2f8e0 000007f8ed06526d clr!SVR::gc_heap::gc1+0x323, calling clr!SVR::GCStatistics::Enabled
0000008e25b2f940 000007f8ecfbe0b3 clr!SVR::gc_heap::bgc_thread_function+0x83, calling clr!CLREventBase::Wait
0000008e25b2f980 000007f8ecf3d5b6 clr!Thread::intermediateThreadProc+0x7d
0000008e25b2fd00 000007f8ecf3d59f clr!Thread::intermediateThreadProc+0x66, calling clr!_chkstk
0000008e25b2fd40 000007f8f8281832 kernel32!BaseThreadInitThunk+0x1a
0000008e25b2fd70 000007f8f8aad609 ntdll!RtlUserThreadStart+0x1d
(...)

使用!threads -special命令,我终于发现这些线程是特殊的 GC线程

0:000> !threads -special
ThreadCount:      81
UnstartedThread:  0
BackgroundThread: 49
PendingThread:    0
DeadThread:       21
Hosted Runtime:   no
(...)
 OSID Special thread type
  1  804 DbgHelper 
  2  f48 GC 
  3  3f8 GC 
  4 1380 GC 
  5  af4 GC 
  6 1234 GC 
  7  fac GC 
  8 12e4 GC 
  9 17fc GC 
 10  644 GC 
 11 16e0 GC 
 12  6cc GC 
 13  9d4 GC 
 14  f7c GC 
 15  d5c GC 
 16  d74 GC 
 17  8d0 GC 
 18 1574 GC 
 19  8e0 GC 
 20  5bc GC 
 21  82c GC 
 22  e4c GC 
 23 129c GC 
 24  e28 GC 
 25  45c GC 
 26  340 GC 
 27 15c0 GC 
 28 16d4 GC 
 29  f4c GC 
 30 10e8 GC 
 31 1350 GC 
 32  164 GC 
 33 1620 GC 
 34 1444 Finalizer 
 35  c2c ProfilingAPIAttach 
 62   50 Timer 
 64 14a8 GC 
 65 145c GC 
 66  cdc GC 
 67  af8 GC 
 68 12e8 GC 
 69 1398 GC 
 70  e80 GC 
 71  a60 GC 
 72  834 GC 
 73  1b0 GC 
 74  2ac GC 
 75  eb8 GC 
 76  ec4 GC 
 77  ea8 GC 
 78   28 GC 
 79 11d0 GC 
 80 1700 GC 
 81 1434 GC 
 82 1510 GC 
 83   9c GC 
 84  c64 GC 
 85 11c0 GC 
 86 1714 GC 
 87 1360 GC 
 88 1610 GC 
 89  6c4 GC 
 90  cf0 GC 
 91 13d0 GC 
 92 1050 GC 
 93 1600 GC 
 94 16c4 GC 
 95 1558 GC 
 96 1b74 IOCompletion 
 97  ce4 ThreadpoolWorker 
 98 19a4 ThreadpoolWorker 
 99 1a00 ThreadpoolWorker 
100 1b64 ThreadpoolWorker 
101 1b38 ThreadpoolWorker 
102 1844 ThreadpoolWorker 
103 1b90 ThreadpoolWorker 
104 1a10 ThreadpoolWorker 
105 1894 Gate 

超过60个“GC”线程...所以我检查了不同服务实例的设置,并发现有问题的设置配置了GC Server,而其他服务实例没有配置。

更多信息:

  • 我们使用.NET 4.5
  • 我们在所有计算机上使用Windows 2012 Server
  • 我们运行双核心服务器(2个CPU,16个物理核心,32个逻辑核心)

我现在要做的是:

  • 我正在尝试让其他人转储(当程序有更多线程时,程序会挂起等等)。
  • 我会尝试在有问题的实例上停用GC Server设置,但问题可能需要一段时间才会发生。

所以这是我的问题

  • GC服务器配置的.NET程序拥有如此多的GC线程是否正常?我以为Server GC每个处理器只有一个GC线程。
  • 这可能与我在这些服务上看到的问题有关,即随着时间的推移数百个线程,由于争用导致巨大的进程冻结?

2 个答案:

答案 0 :(得分:1)

使用Server GC,每个逻辑核心将有一个线程(该核心为affinity set)。所以在你的情况下应该至少有32个线程。如果您打开了后台GC,则可能有更多工作线程处理每个堆的图形(reference)。

还要记住,这些GC线程将在THREAD_PRIORITY_HIGHEST运行,这很容易使任何尚未被GC暂停的线程(reference)饿死。

现在,就你的其他线程而言,无论垃圾收集器如何,一个进程中的500+都会产生很多争用。因此,弄清楚这些线程对你的调查很重要。


要注意的事项

  • 查看后台GC是否已打开,如果是,请尝试在没有它的情况下运行(4.5的服务器GC支持此模式)。
  • 尝试减少线程池上的最大线程数(32767是一个不健康的最大值)。

您还可以使用procdump.exe来帮助在性能下降时捕获小型转储。

答案 1 :(得分:0)

我在NUMA服务器上遇到过类似的问题。帮助我的事情:

  • 限制线程池
  • 限制挂起的托管进程的处理器关联掩码。它看起来很奇怪但是减少进程的处理器数量有时会使它在高并发负载下运行得更快。我怀疑旋锁(忙等待)。