TOC
GMP调度模型
设计策略
- 复用线程
避免频繁创建、销毁线程,而是对线程的复用。
- work stealing 机制
当本地线程无可运行的G时,尝试从其它线程绑定的P中偷取G(如果Global Queue有待运行的G,就先从Global队列中去取),而不是销毁线程。
- hand off 机制
当本线程M因为G进行系统调用阻塞时,线程就先释放绑定的P,把P转移到其它空闲的线程M执行。
- 利用并行
GOMAXPROCS 设置P的数量,最多有GOMAXPROCS 个线程分布在多个CPU上同时运行(runtime/debug 中的SetMaxThreads 设置M最大数量)。GOMAXPROCS 也限制了并发的程度, 比如GOMAXPROCS = 物理核数/2,最多利用一半的CPU核进行并行。
- 抢占式任务处理
在coroutine中要等待一个协程主动让出CPU才执行下一个协程,在Go中一个goroutine最多占用CPU 10ms,防止其它goroutine被饿死。
- 全局G队列
当一个G被创建时,先尝试加入P的本地G队列,如果局部队列已满则加入全局G队列。
go func()调度流程
- go func()创建一个goroutine;
- 新创建的G先尝试加入P的本地G队列,如果局部队列已满则加入全局G队列;
- G只能在M中运行,一个M必须持有一个P。M从与它绑定的P中获取可执行状态的G,如果P本地队列为空,则从全局G队列或其它MP组合中偷取可执行的G;
- M调度G,执行G.func(),顺利执行完后销毁G,返回M响应;
- 当M执行G时发生了系统调用(syscall)或其余阻塞操作,M会阻塞,runtime会把这个M绑定的P摘除(detach),然后再创建一个新M(如果有空闲的M,优先复用空闲的M)与被摘除的这个P绑定;
- 当M的系统调用结束后,这个G会尝试获取一个空闲的P执行,并放入到这个P的本地队列。如果获取不到P,那么这个线程M变成休眠状态,加入到空闲线程中,然后这个G会被放入全局队列中。
调度器的生命周期
M0
M0是启动程序后编号为0的主线程,这个M对应的实例会在全局变量runtime.m0中,不需要再heap上分配,M0负责执行初始化操作和启动第一个G,在之后M0就和其它M一样了。
G0
G0每次启动一个M都会第一个创建的goroutine,每个M都会有一个自己的G0,G0仅用于负责调度其它G,G0不指向任何可执行的函数。在调度或系统调用时会使用G0的栈空间,全局变量的G0是M0的G0.
调度器调度典型场景
- 有子协程的创建:局部性原理,P拥有G1,M1获取P后开始运行G1,G1使用go func()创建了G2,为了局部性G2优先加入到P1的本地队列。
- G0协程切换:G1运行完成后(函数:goexit),M上运行的goroutine切换为G0,G0负责调度时协程的切换(函数:schedule)。从P的本地队列取G2,从G0切换到G2,并开始运行G2(函数:execute)。实现了线程M1的复用。
- 本地G队列已满:负载均衡把已满的本地队列中前一半的G,还有新创建的G一起转移到全局G队列(顺序会被打乱)。
- 自旋线程:在创建G时,运行的G会尝试唤醒其他空闲的P和M组合。被唤醒的M绑定了P,并运行了G0,但其本地队列没有G,此时这个被唤醒的M为自旋线程(没有G但为运行状态的线程,不断寻找G)。
- 从全局队列到P本地队列的负载均衡:M1尝试从全局队列取一批G放到P2的本地队列(函数:findrunnable())。M1从全局队列取的G数量n = min(len(GQ)/GOMAXPROCS + 1, len(GQ/2))。
- work stealing:在场景5的情况下,全局队列如果没有G,那m就要执行work stealing(偷取):从其他有G的P哪里偷取一半G过来,放到自己的P本地队列。
- 所有P的本地队列都为空:最多有GOMAXPROCS个自旋的线程等待处理G,当有新goroutine创建时,立刻能有M运行它,如果销毁再新建就增加了时延,降低了效率。
- 因为永远是M>=P,大部分都是M在抢占需要运行的P: G1运行时创建了G2,G1进行了阻塞的系统调用,M1和P1立即解绑,P1会执行以下判断:如果P1本地队列有G、全局队列有G或有空闲的M,P1都会立马唤醒1个M和它绑定,否则P1则会加入到空闲P列表,等待M来获取可用的p。
- G1创建了G2,G1进行了非阻塞系统调用: M1和P1会解绑,但M1会记住P1,然后G1和M1进入系统调用状态。当G1和M1退出系统调用时,会尝试获取P1,如果无法获取,则获取空闲的P,如果依然没有,G1会被记为可运行状态,并加入到全局队列,M1因为没有P的绑定而变成休眠状态(长时间休眠等待GC回收销毁)。
comments powered by Disqus