大家在分析 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