调度模型---MPG
image.png- M(工作线程,它由系统调度): 一般比P个数多,做一些其他处理(如:runtime包的内置其他任务需要处理,当某个G发生系统调度产生阻塞时,多出来的M会接管剩余的本地G队列),必须持有P才可以执行代码.
- P(处理器,包含运行go代码的必要资源和调度
goroutine
的能力): 一般小于等于cpu
核数, 除了调度本地的runqueues
还会周期性的调用全局的global runqueue
- G(协程,由go关键字创建的): 分为两个全局的
G
队列(global runqueue
) 和每个P
自己维护的G
队列(runqueues
)GO1.1
之前只有全局的G队列,多个处理器P
通过互斥锁调度,严重影响了并发执行效率. 引入局部runqueues
后,每个处理器P
访问自己的runqueuues
时不需要加锁,大大提高了效率.
调度策略
- 队列轮转:
每个处理器P调度自己维护的队列中的G到M中执行,执行结束则继续调度下一个,另外会定期检查global runqueue
中待运行的G并调度到M执行,全局的global runqueue
主要来自从系统调用恢复的G.周期性检测防止G的得不到调度机会. - 系统调用
当某个M所持有的P所调度的G,发生系统调用时,工作线程将会阻塞,即M将会阻塞,所以M个数要比P多,当发生阻塞时,在冗余的M中挑选一个接管P调度剩下的G,即做工作交接.当G系统调度结束后,如果有空闲的P,原M则会获取一个P,继续执行G,否则将G放入全局的global runqueue
,并将M放入到缓存池. - 工作量窃取
即某个处理器P没有需要调度的协程时,将从其他处理器中偷取一半的协程 - 抢占式调度
避免某个协程长时间执行,而阻碍其他协程被调度的机制.在GO1.14之前如果协程没有函数调用会无限的占用执行权.如
go func() {
for {
// 无函数调用
}
}
直到GO1.14,调度器引入了基于信号的抢占机制,这个问题才得以解决.
网友评论