启用或停止M
相关函数
stopm(): 停止当前M的执行,直到因有新的G变得可运行而被唤醒
gcstopm(): 为串行运行时任务的执行让路,停止当前M的执行。串行运行时任务执行完毕后会被唤醒
stoplockedm(): 停止已与某个G锁定的当前M的执行,直到因这个G变得可运行的而被唤醒
startlockedm(gp *g): 唤醒与gp锁定的那个M,并让该M去执行gp
startm(_p_ *p, spinning bool): 唤醒或创建一个M去关联_p_并开始执行
调度器图例
启用或停止M图例
步骤
(1)
调度器在执行调度流程的时候,会先检查当前M是否与某个G锁定,如果锁定存在,调度器就会调用stoplockedm函数停止当前M
stoplockedm函数先解除当前M与本地P之间的关联,并通过调用一个名为handoffp的函数把这个P转手给其他M,在这个转手P的过程中会简介调用startmh函数
一旦这个P被转手,stoplockedm函数就会停止当前M的执行,并等待唤醒
(2)
另一方面,如果调度程序为当前M找到了一个可运行的G,却发现该G已与某个M锁定了,那么就会调用startlockedm函数并把这个G作为参数传入
startlockedm函数会通过参数gp的lockedm字段找到与之锁定的那个M(已锁的M),并把当前M的本地P转手给它
这里的转手P的过程要比(1)中的简单很多,startlockedm函数会先解除当前M与本地P之间的关联,然后把这个P赋给已锁M的nextp字段
(3)
startlockedm函数的执行会使与其参数gp锁定的那个M(已锁M)被唤醒
通过gp的lockedm字段可以找到已锁M。一旦已锁M被唤醒,就会与和它预联的P产生正式的关联,并去执行与之关联的G
(4)
startlockedm函数在最后会调用stopm函数。stopm函数会先把当前M放入调度器的空闲M列表,然后停止当前M。这里被停止的M,可能会在之后因有P需要转手,或有G需要执行而被唤醒
从另一个角度看,一旦M要停止就会把它的本地P转手给别的M。一旦M被唤醒,就会先找到一个P与之关联,即找到它的新的本地P
并且,这个P一定是在该M被唤醒之前由别的M预联给它的。因此,P总是会被高效利用
如果handoffp函数无法把作为其参数的P转给一个M,那么就会把这个P放入调度器的空闲P列表。该列表中的P会在需要时(比如有G需要执行)被取用
(5)
调度器在执行调度器流程的时候,也会检查是否有串行运行时任务正在等待执行
如果有,调度器就会调用gcstopm函数停止当前M
gcstopm函数会通过当前M的spinning字段检查它的自旋状态,如果其值为true,就把false赋给它,然后把调度器中用于记录自旋M数量的nmspinning字段的值减1
如此一来就完全重置了当前M的自旋状态标识,一个将要停止的M理应脱离自旋状态。在这之后,gcstopm函数会释放本地P,并将其状态设置为Pgcstop
然后再去自减并检查调度器的stopwait字段,并在发现stopwait字段的值为0时,通过stopnote字段唤醒等待执行的串行运行时任务
(6)
gcstopm函数在最后会调用stopm函数。同时,当前M会被放入调度器的空闲列表并停止
只要有串行运行时任务准备执行,"Stop the world"就会开始,所有在调度过程中的M就会执行步骤(5)和(6)
其中的步骤(5)更是决定了串行运行时任务是否能够被尽早地执行
(7)
调度总有不成功的时候。如果经过完整的一轮调度之后,仍找不到一个可运行的G给当前M执行,那么调度器程序就会通过调用stopm函数停止当前的M
一旦停掉的M被唤醒,stopm函数就会负载关联它和已与它预联的P,这也是在为M的执行做最后的准备
还有一种情况,如果stopm函数被发现当前M是因有可并发执行的GC任务而被唤醒的,那么就在执行完该任务之后再次停止当前M
(8)
所有经由调用stopm函数停止的M,都可以通过调用startm函数。与步骤(7)对应,一个M被唤醒的原因总是有新工作要做
比如,有了新的自由的P,或者有了新的可运行的G。有时候,传入startm函数的参数_p_为nil,这就说明在唤醒一个M的同时,需要从调度器的空闲P列表获取一个P作为M运行G的上下文环境
如果这个列表已经空了,那么startm函数也就无能为力了(没有上下文环境有了M也没有用),这时startm函数会直接返回
一旦有了一个P,startm函数就会再从调度器的空闲M列表获取一个M。如果该列表已空就创建一个新的M
无论如何,startm函数都会把拿到的P和这个M预联,然后让该M做好执行准备