深入理解Go语言
上QQ阅读APP看书,第一时间看更新

1.2.2 调度器的设计策略

1.策略一:复用线程

避免频繁地创建、销毁线程,而是对线程的复用。

1)偷取(Work Stealing)机制

当本线程无可运行的G时,尝试从其他线程绑定的P偷取G,而不是销毁线程,如图1.13所示。

这里需要注意的是,偷取的动作一定是由P发起的,而非M,因为P的数量是固定的,如果一个M得不到一个P,那么这个M是没有执行的本地队列的,更谈不上向其他的P队列偷取了。

2)移交(Hand Off)机制

当本线程因为G进行系统调用阻塞时,线程会释放绑定的P,把P转移给其他空闲的线程执行,如图1.14所示,此时若在M1的GPM组合中,G1正在被调度,并且已经发生了阻塞,则这个时候就会触发移交的设计机制。GPM模型为了更大程度地利用M和P的性能,不会让一个P永远被一个阻塞的G1耽误之后的工作,所以遇见这种情况的时候,移交机制的设计理念是应该立刻将此时的P释放出来。

图1.13 偷取机制

图1.14 移交机制:G1发生阻塞

如图1.15所示,为了释放P,所以将P和M1、G1分离,M1由于正在执行当前的G1,全部的程序栈空间均在M1中保存,所以M1此时应该与G1一同进入阻塞的状态,但是已经被释放的P需要跟另一个M进行绑定,所以就会选择一个M3(如果此时没有M3,则会创建一个新的或者唤醒一个正在睡眠的M)进行绑定,这样新的P就会继续工作,接收新的G或者从其他的队列中实施偷取机制。

2.策略二:利用并行

GOMAXPROCS设置P的数量,最多有GOMAXPROCS个线程分布在多个CPU上同时运行。GOMAXPROCS也限制了并发的程度,例如GOMAXPROCS=核数/2,表示最多利用一半的CPU核进行并行。

3.策略三:抢占

在Co-routine中要等待一个协程主动让出CPU才执行下一个协程,在Go中,一个Goroutine最多占用CPU 10ms,防止其他Goroutine无资源可用,这就是Goroutine不同于Co-routine的一个地方,如图1.16所示。

图1.15 移交机制:阻塞的P被释放

4.策略四:全局G队列

在新的调度器中依然有全局G队列,但功能已经被弱化了,当M执行偷取,但从其他P偷不到G时,它可以从全局G队列获取G,如图1.17所示。

图1.16 Goroutine的抢占策略

图1.17 全局队列获取