.NET托管线程的 Preemptive和Cooperative 状态解析

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