GO协程调度

调度模型---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,调度器引入了基于信号的抢占机制,这个问题才得以解决.

你可能感兴趣的:(GO协程调度)