一、协程设计-GMP模型

GMP

1.工作线程M

工作线程是最终运行协程的实体。操作系统中的线程与在运行时代表线程的m结构体进行了绑定:

curg
g0ggg->g0->g
thread-local storagetlsm.tls

2.逻辑处理器p

runtime.GOMAXPROCS()

逻辑处理器p通过结构体p进行定义:

idstatusmm==nil
runqp.runqheadp.runqtailschedt.runq
runnextp.runnextrunnextp.runq

3.协程g

协程通常分为特殊的调度协程g0以及执行用户代码的普通协程g。

无论g0还是g,都通过结构体g进行定义:

stackmschedgobuf

4.全局调度信息schedt

schedt
schedtmidlenmidlefreemngsyspidlenpidlerunqrunqsizegFree
schedtrunqschedtlock

5.GMP详细示图

通过上述说明,我们可以进一步细化GMP模型示图为:

二、协程调度

g0g->g0->g->g0-g

1.调度策略

工作线程m需要通过协程调度获得具体可运行的某一协程g。

获取协程g的一般策略主要包含三大步:

  • 1. 查找p本地的局部运行队列
  • 2. 查找schedt中的全局运行队列
  • 3. 窃取其他p中的局部运行队列
findRunnable()

获取本地运行队列

runqget()
runnextrunnextrunqrunqheadrunqtail

协程调度时由于总是优先查找局部运行队列中的协程g,如果只是循环往复的地执行局部队列中的g,那么全局队列中的g可能一个都不会被调度到。

因此,为了保证调度的公平性,p中每执行61次调度,就会优先从全局队列中获取一个g到当前p中执行:

获取全局运行队列

runqput

协程窃取

allp []*p
runqstealrunqgrab

2.调度时机

调度策略让我们知道了协程是如何调度的,下面继续说明什么时候会发生协程调度。

主动调度

runtime.Gosched()
_Grunning_Grunnabledropg()globrunqput()schedule()

被动调度

gopark()gopark()park_m()
_Grunning_Gwaitingdropg()waitunlockffalseschedule()
_Gwaiting_Gwaiting_Grunnablegoready()ready()

抢占调度

go应用程序在启动时会开启一个特殊的线程来执行系统监控任务,系统监控运行在一个独立的工作线程m上,该线程不用绑定逻辑处理器p。系统监控每隔10ms会检测是否有准备就绪的网络协程,并放置到全局队列中。

大于10ms大于20微秒retake()