什么是Go中的GMP模型
GMP模型是Go语言调度器中的并发编程模型,通过G, M, P的协同工作,组成了Go语言的并发编程解决方案。
GMP模型概览
在开始讲GMP模型前,我们先来看一个简单点的GM模型。
在Go 1.1版本之前,其实使用的是GM模型,即多个线程去处理多个协程,其中G协程是存放在一个全局队列中的,我们知道,多个线程去竞争一个队列中的协程时,会造成冲突,因此势必要通过加锁的方式去获取协程,这样的话会造成多个线程之间的冲突,降低性能。
因此,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不和自旋线程进行绑定?
来源:【B站】刘丹冰Aceld
因为自旋线程已经有了与其绑定的P,自旋线程想要抢占的是G,而不是P。
阻塞的G syscall结束后,转为非阻塞状态的情况
当G阻塞完成后, 优先找原来的P配对,但此时P大概率已经被别的M所占用;G会再去空闲P队列中找空闲的P,找到则配对,找不到则GG,此时M和G也要解绑,G被放入全局队列中,M被放入休眠M队列中。