TL; DR:启用了Server GC的应用程序会显示数十个特殊的 GC线程并挂起超时。有什么可以解释的?
这些天我一直处于.NET服务上发生的奇怪的多线程/争用问题上。症状如下:
我立即怀疑我们的代码中存在一个问题,该问题会导致托管线程池随着时间的推移启动大量线程,所有这些都试图共享一个或多个公共资源。看起来我们对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
,而其他服务实例没有配置。
更多信息:
我现在要做的是:
GC Server
设置,但问题可能需要一段时间才会发生。所以这是我的问题:
答案 0 :(得分:1)
使用Server GC,每个逻辑核心将有一个线程(该核心为affinity set)。所以在你的情况下应该至少有32个线程。如果您打开了后台GC,则可能有更多工作线程处理每个堆的图形(reference)。
还要记住,这些GC线程将在THREAD_PRIORITY_HIGHEST
运行,这很容易使任何尚未被GC暂停的线程(reference)饿死。
现在,就你的其他线程而言,无论垃圾收集器如何,一个进程中的500+都会产生很多争用。因此,弄清楚这些线程对你的调查很重要。
要注意的事项
您还可以使用procdump.exe来帮助在性能下降时捕获小型转储。
答案 1 :(得分:0)
我在NUMA服务器上遇到过类似的问题。帮助我的事情: