go线程实现模型

摘自:go并发编程实战 第二版

核心元素:

M

machine的缩写,一个M代表一个内核线程,或“工作线程”。在大多数情况下,创建一个M,都是由于没有足够的M来关联P并运行其中可运行的G。在运行时系统执行系统监控或垃圾回收等任务时,也会导致M的创建。

P

processor的缩写。一个P代表执行一个Go代码片段所必须的资源(或称"上下文环境")。P是G能够在M中运行的关键,go的运行时系统会适时让P与不同的M建立或者断开关联,以使P中那些可运行的G能够及时获得运行时机,这与操作系统内核在cpu上实时的切换不同进程或者线程类似。

改变单个go程序间接拥有的P的最大数量有两种办法。

1.调用系统的runtime.GOMAXPROCS并把想要设定的数量作为参数传入;

2.在go程序运行前设置环境变量GOMAXPROCS的值。

P的最大数量实际上是对程序中并发运行的G的规模的一种限制。P的数量即是可运行的G的队列的数量,一个G在被启用之后,会先被追加到某个P的可运行的G队列中,以等待运行时机。一个P只有与M相关联在一起,才会使其可运行G队列中的G有机会运行。不过,设置P的最大数量只能限制P的数量,而对G和M的数量没有任何约束。当M因系统调度而阻塞(确切的说,M运行的G进入了系统调用)的时候,运行时系统会把该M和与之关联的P分离开。这时,如果这个P的可运行G队列中还有未被运行的G,那么运行时系统就会找一个空闲的M,或者创建一个新的M并与该P关联以满足这些G的运行需要。因此M的数量很多时候都会比P多。,而G的数量,取决于程序本身。

P的状态有如下:

1.pidle:当前P未与任何M存在关联

2.prunning:当前P正在与某个M关联

3.psyscall:当前P中的运行的那个G正在进行系统调用。如运行时系统在开始gc的某些步骤前,会试图把全局P列表中的所有P都置于这个状态。

4.pdead,当前P已经不会再被使用,如果在go程序运行的过程中,通过runtime.GOMAXPROCS的函数减少了P的最大数量,那么多余的P的数量就会被运行时系统置于该状态。

5.pgcstop:运行时系统需要停止调度

P在创建之初的状态是pgcstop,并不意味着运行时系统在这时进行gc。不过P处于这一初始状态的时间会很短暂,在紧接着的初始化后,运行时系统会将其状态置为pidle,并放入调度器的空闲P列表。

由图可见,非pdead状态的P都会在运行时系统停止调度时被置于pgcstop状态。等到需要重启调度的时候(如gc结束后),他们并不会被恢复至原有状态,而会被统一的转换为pidle。等待被再次调度。

非pgcstop状态的P都可能因全局P列表的缩小而被认为是多余的被置为pdead。此时不必担心其中的G会失去归宿,因为在P被转换为pdead状态之前,其可运行G队列中的G都会被转移到调度器的可运行G队列,而它的自由G列表中的G也都会被转移到调度器的自由G列表中。

每个P中除了都有一个可运行的G队列,还都包含一个自由G列表,这个列表中包含了一些已经运行完成的G。随着运行完成的G的增多,该列表可能会很大。当增长到一定成都,运行时系统就会把其中的部分G转移到调度器的自由G列表。

当使用go语句来启用一个G的时候,运行时系统会先试图从相应的P的自由G列表中获取一个现成的G,来封装这个go语句。当且仅当获取不到这样一个G的时候才会创建按一个新的G。如果P的自由G列表为空无法获取到自由G,运行时系统会在调度器的自由G列表中转移一些过来。

因此,只有当调度器的自由G列表为空的时候,才会有新的G被创建,提高了G的复用率。

在P的结构中,可运行G队列和自由G列表是最重要的成员。

G

goroutine的缩写。一个G代表一个Go代码片段。

一个goroutine的执行需要P和M的支持,一个M在与P关联后,就形成了一个有效的G运行环境(内核线程+上下文环境)。每个P都会包含一个可运行的G的队列,该队列的G会被依次传递给与本地P关联的M,并获得运行时机。

猜你喜欢

转载自blog.csdn.net/u010918487/article/details/86065731