MPG 模型概述
Golang 中的 MPG 线程模型对两级线程模型进行一定程度的改进,使它能够更加灵活地进行线程之间的调度。它由三个主要模块构成,如下图所示:
machine,一个 machine 对应一个内核线程,相当于内核线程在 Golang 进程中的映射
processor,一个 prcessor 表示执行 Go 代码片段的所必需的上下文环境,可以理解为用户代码逻辑的处理器
goroutine,是对 Golang 中代码片段的封装,其实是一种轻量级的用户线程。
为了减轻描述工作,下面的介绍中我们会用 M、P、G 分别指代 machine、processor 和 goroutine。
每一个 M 都会以一个内核线程绑定,M 和 P 之间也是一对一的关系,而 P 和 G 的关系则是一对多。在运行过程中,M 和 内核线程之间对应关系的不会变化,在 M 的生命周期内,它只会与一个内核线程绑定,而 M 和 P 以及 P 和 G 之间的关系都是动态可变的。
在实际的运行过程中,M 和 P 的组合才能够为 G 提供有效的运行环境,而多个可执行 G 将会顺序排成一个队列挂在某个 P 上面,等待调度和执行,如下图所示:
上图中,M 和 P 共同构成了一个基本的运行环境,此时 G0 中的代码片段处于正在运行的状态,而右边的 G 队列处于待执行状态。
M 的创建一般是因为没有足够的 M 来和 P 组合以为 G 提供运行环境,在很多时候 M 的数量可能会比 P 要多。在单个 Golang 进程中,P 的最大数量决定了程序的并发规模,且 P 的最大数量是由程序决定的。可以通过修改环境变量 GOMAXPROCS 和 调用函数 runtime#GOMAXPROCS 来设定 P 的最大值。
M 和 P 会适时的组合和断开,保证 P 中的待执行 G 队列能够得到及时运行。比如说上图中的 G0 此时因为网络 I/O 而阻塞了 M,那么 P 就会携带剩余的 G 投入到其他 M 的怀抱中。这个新的 M1 可能是新创建的,也可能是从调度器空闲 M 列表中获取的,取决于此时的调度器空闲 M 列表中是否存在 M,从而避免 M 的过多创建,如下图所示:
当 M 对应的内核线程被唤醒时,M 将会尝试为 G0 捕获一个 P 上下文,可能是从调度器的空闲 P 列表中获取,如果获取不成功,M 会被 (根据自己的理解感觉应该把 M会被 替换为 M会把)G0 放入到调度器的可执行 G 队列中,等待其他 P 的查找。为了保证 G 的均衡执行,非空闲的 P 会运行完自身的可执行 G 队列中,会周期性从调度器的可执行 G 队列中获取代执行的 G,甚至从其他的 P 的可执行 G 队列中掠夺 G。
有个疑问:
对于 Python 里的 eventlet 或者 Go 里 goroutine, 他们的并发到底能使用到多核吗?
根据 两级线程模型 的描述感觉是可以的呀,最起码切换到内核态的时候是可以在多核上并行的,可以辅助参考 为什么多线程可以利用到多核