大家在分析 dump 时,总少不了用 !t 命令看线程列表,比如下面的输出。.
0:000> !t
ThreadCount: 4
UnstartedThread: 0
BackgroundThread: 3
PendingThread: 0
DeadThread: 0
Hosted Runtime: no
Lock
DBG ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception
0 1 558 008DB6B8 2a020 Cooperative 0299ACAC:0299BD60 00955aa8 -00001 MTA
6 2 16ec 00966000 2b220 Preemptive 00000000:00000000 00955aa8 -00001 MTA (Finalizer)
7 3 3e34 0097F0F0 102a220 Preemptive 00000000:00000000 00955aa8 -00001 MTA (Threadpool Worker)
8 4 269c 05C0D688 1029220 Preemptive 00000000:00000000 00955aa8 -00001 MTA (Threadpool Worker)
从输出信息看,有一列叫 GC Mode ,可以观察到它的状态值分别为 Cooperative 和 Preemptive,那这两个状态到底代表着什么呢?为了更好的演示,我先上一段它的测试代码:
public static void Main()
{
while (true)
{
}
Console.ReadLine();
}
从 GC Mode 名字看,应该和 GC 回收有关,确实是这样,接下来我们一一解读下。
1. Preemptive
翻译过来就是 抢占,那 抢占 谁呢?很显然是抢占 托管线程,让它处于某一种抑制状态,简而言之就是如果一个 托管线程 切换到 Preemptive 之后,此时它就无法访问 托管堆 了,这么做的好处,自然就是让 GC 在托管堆上自由的标记,在计划,回收和压缩阶段而不受托管线程干扰,目前来说有两种情况线程会处于 Preemptive 状态。
1)GC 触发阶段
这个我已经聊过了,GC 要标记对象可达时,必须要让所有线程处于暂停(Preemptive)状态。
2) 切换到非托管代码
我们在用 C# 编程时,经常要和 非托管代码 交互,比如 C++,Python,换句话说,线程可能需要从托管层切换到非托管层,当切换到后者时,CLR也需要将线程从 Cooperative 切换到 Preemptive。
2. Cooperative
翻译过来就是 协作,那谁和谁协作呢?自然就是 GC 和 用户线程 合作,这种状态的 线程 是可以自由访问 托管堆 ,经常在 GC 回收结束后,将线程转成切换到 Cooperative 。
不知道大家现在可明白啦,最后补充一下,如果你看到所有的线程都处于 Preemptive 状态,有很大可能是 GC 触发啦,比如下面这样。
0:295> !t
ThreadCount: 200
UnstartedThread: 180
BackgroundThread: 19
PendingThread: 180
DeadThread: 0
Hosted Runtime: no
Lock
ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception
0 1 17a8 000001f1c7065990 26020 Preemptive 0000000000000000:0000000000000000 000001f1c703bdd0 0 STA
2 2 2114 000001f1c708ef50 2b220 Preemptive 0000000000000000:0000000000000000 000001f1c703bdd0 0 MTA (Finalizer)
4 3 1f70 000001f1c72b7ba0 1029220 Preemptive 0000000000000000:0000000000000000 000001f1c703bdd0 0 MTA (Threadpool Worker)
5 4 2058 000001f1e5b1e840 1039220 Preemptive 0000000000000000:0000000000000000 000001f1c703bdd0 0 Ukn (Threadpool Worker)
6 5 2860 000001f1e5b4cb40 2b220 Preemptive 0000000000000000:0000000000000000 000001f1c703bdd0 0 MTA
9 6 a18 000001f1e5b83470 2b220 Cooperative 0000000000000000:0000000000000000 000001f1c703bdd0 1 MTA (GC)
8 7 2aec 000001f1e5b593b0 1039220 Preemptive 0000000000000000:0000000000000000 000001f1c703bdd0 0 Ukn (Threadpool Worker)
10 10 2090 000001f1e5b27910 1039220 Preemptive 0000000000000000:0000000000000000 000001f1c703bdd0 0 Ukn (Threadpool Worker)
11 11 99c 000001f1e5b85af0 1039220 Preemptive 0000000000000000:0000000000000000 000001f1c703bdd0 0 Ukn (Threadpool Worker)
12 12 170c 000001f1e5ba5b20 1039220 Preemptive 0000000000000000:0000000000000000 000001f1c703bdd0 0 Ukn (Threadpool Worker)
13 13 1798 000001f1e5b39360 1039220 Preemptive 0000000000000000:0000000000000000 000001f1c703bdd0 0 Ukn (Threadpool Worker)
14 14 2ae4 000001f1e5b31b70 1029220 Preemptive 0000000000000000:0000000000000000 000001f1c703bdd0 0 MTA (Threadpool Worker)
15 15 2ebc 000001f1e5b30320 1039220 Preemptive 0000000000000000:0000000000000000 000001f1c703bdd0 0 Ukn (Threadpool Worker)
16 16 e00 000001f1e5ba24e0 1029220 Preemptive 0000000000000000:0000000000000000 000001f1c703bdd0 0 MTA (Threadpool Worker)
17 17 123c 000001f1e5ba2cb0 1029220 Preemptive 0000000000000000:0000000000000000 000001f1c703bdd0 0 MTA (Threadpool Worker)
18 18 2aa0 000001f1e5b67430 1039220 Preemptive 0000000000000000:0000000000000000 000001f1c703bdd0 0 Ukn (Threadpool Worker)
19 19 2990 000001f1ef8597a0 1039220 Preemptive 0000000000000000:0000000000000000 000001f1c703bdd0 0 Ukn (Threadpool Worker)
20 20 1568 000001f1ef857860 1029220 Preemptive 0000000000000000:0000000000000000 000001f1c703bdd0 0 MTA (Threadpool Worker)
21 21 1f20 000001f1ef85a740 21220 Preemptive 0000000000000000:0000000000000000 000001f1c703bdd0 0 Ukn
115 26 11e4 000001f1efb5bea0 8029220 Preemptive 0000000000000000:0000000000000000 000001f1c703bdd0 0 MTA (Threadpool Completion Port)
116 24 2d34 000001f1efb5a730 1400 Preemptive 0000000000000000:0000000000000000 000001f1c703bdd0 0 Ukn