概述
go语言中的MPG线程模型对两级线程模型进行了一定程度的改进,使它能够更加灵活的进行线程之间的调度。
它由3个主要模块构成,如下图:

MPG的3个主要模块以及功能,我们通过下表所示。
模块 | 功能说明 |
Machine | 一个Machine对应一个内核线程,相当于内核线程在go语言中的映射 |
Processor | 一个Processor表示执行go代码片段的所必需的上下文环境,可以理解为用户代码逻辑的处理器 |
Goroutine | 是对go语言中代码片段的封装,其实是一种轻量级的用户线程 |
为了更加形象,下面的介绍中我们会用M、P、G分别指代Machine、Processor和Goroutine。从图4-4可以看出,每一个
M都会与一个内核线程绑定,在运行时一个M同时只绑定一个P,而P和G的关系则是一对多。在运行过程中,M和内核线程之间
对应关系不会变化,在M的生命周期内,它只会与一个内核线程绑定,而M和P以及P和G之间的关系都是动态可变的。
在实际的运行过程中,M和P的组合才能够为G提供有效的运行环境,而多个可执行G将会顺序排成一个队列挂在某个P上面,
等待调度和执行,如图所示。
上图中,M和P共同构成了一个基本的运行环境,此时G0中的代码片段处于正在运行的状态,而右边的G队列处于待执行状态。
当没有足够的M来和P组合为G提供运行环境时,Go语言会创建新的M。在很多时候M的数量可能会比P要多。在单个go语言进程中,
P的最大数量决定了程序的并发规模,且P的最大数量是由程序决定的。可以通过修改环境变量GOMAXPROCS和调用函数runtime.GOMAXPROCS
来设定P的最大值。
M和P会适时的组合和断开,以保证待执行G队列能够得到及时运行。比如说图4-5中的G0此时因为网络I/O而阻塞了M,那么P就会携带剩余的
G投入到其他M中。这个新的M(M1)可能是新创建的,也可能是从调度器空闲M列表中获取的,这取决于此时的调度器空闲M列表中是否存在M,
这样的机制设计也是为了避免M过多创建。运行机制如图所示
当M对应的内核线程被唤醒时,M将会尝试为G0捕获一个P上下文,可能是从调度器的空闲P列表中获取,如果获取不成功,
M会把G0放入到调度器的可执行G队列中,等待其他P的查找。为了保证G的均衡执行,非空闲的P运行完自身的可执行G队列后,
会周期性从调度器的可执行G队列中获取待执行的G,甚至从其他的P的可执行G队列中掠夺G。
-------------------------------------------