Go中的GMP模型| 青训营笔记

241 阅读4分钟

什么是Go中的GMP模型

GMP模型是Go语言调度器中的并发编程模型,通过G, M, P的协同工作,组成了Go语言的并发编程解决方案。

GMP模型概览

在开始讲GMP模型前,我们先来看一个简单点的GM模型。

在Go 1.1版本之前,其实使用的是GM模型,即多个线程去处理多个协程,其中G协程是存放在一个全局队列中的,我们知道,多个线程去竞争一个队列中的协程时,会造成冲突,因此势必要通过加锁的方式去获取协程,这样的话会造成多个线程之间的冲突,降低性能。

image.png 来源:juejin.cn/post/695600…

因此,GMP模型在将处理器P考虑进来的同时,为每个P都分配了一个本地队列,线程M想要执行某个G之前,需要先和处理器P进行绑定,绑定后才能执行当前P的本地队列中的G。

P和M的个数关系

P:可以通过环境变量$GOMAXPROCS或者runtime的GOMAXPROCS()方法决定,程序执行时,并行的数量上限是GOMAXPROCS()

M: Go程序在启动时,会设置M的最大数量,默认为10000,但一般不会同时开这么多个M,可能是受限于操作系统内核或者是物理机的资源等原因。可以通过runtime的SetMaxThread()进行设置。

P和M没有绝对的相关性,M一般来说会比P多很多,当一个M阻塞时,P会创建或切换另一个M。

GMP模型的设计思想

新建的G放到哪里

当有新的G时,优先查看P的本地队列是否有空余,放入某个P的本地队列中,若所有本地队列都满了的情况下,则放入全局队列中。

本地队列满的情况

当M对应的P的本地队列中,正在执行协程G2,这个协程开辟了很多的新协程,最终导致本地队列满的情况,调度器将本地队列一分为2,队头部分打散并放入全局队列,为什么不是将队尾部分放进全局队列呢?一是因为防止队尾协程饥饿问题,二是由于由G2新创建的协程很可能和G2有上下文的关系。因此优先把队尾的一半留在本地队列。

什么时候唤醒休眠的M

当有协程去创建新的协程时,会尝试唤醒休眠队列中的M。(存疑)

自旋线程,自旋线程的最大数量限制

自旋线程的最大数量限制取决于GOMAXPROCS,因为M远远多于GOMAXPROCS,因为M远远多于GOMAXPROCS,因为M远远多于GOMAXPROCS,当线程自旋一段时间后发现获取不到新的P资源,会放入休眠队列中,减小自旋带来的性能损耗。

运行中的G发生阻塞时

当运行中的G发生阻塞时,可能是G调用了硬件资源或者进行了系统调用,此时M和G进行绑定,同时要和P进行解绑,因为P资源是很宝贵的,需要让P去和其他的M进行绑定并继续运行。

假设此时,休眠线程队列中有M5和M6,且有2个自旋线程,M3和M4。

解绑后的P会从休眠队列中取一个M5进行绑定,如果休眠队列没有M,则P会被加入到P的休眠队列中,等待M的唤醒。

为什么P不和自旋线程进行绑定?

image.png

来源:【B站】刘丹冰Aceld

因为自旋线程已经有了与其绑定的P,自旋线程想要抢占的是G,而不是P。

阻塞的G syscall结束后,转为非阻塞状态的情况

当G阻塞完成后, 优先找原来的P配对,但此时P大概率已经被别的M所占用;G会再去空闲P队列中找空闲的P,找到则配对,找不到则GG,此时M和G也要解绑,G被放入全局队列中,M被放入休眠M队列中。