一、协程设计-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()