likes
comments
collection
share

图解Go的协程调度(一),被面试官吊打后的总结

作者站长头像
站长
· 阅读数 10

记得2018年的时候,我还在一个小公司窝着。虽然已经在公司工作了4年多,但是我觉得也是时候走出去了。

于是,我就开始投简历面试 Go 相关的职位了。

我还记得那个是一个算不得天气晴朗的午后,我被面试官有关 go 调度的问题问懵的情景。

面试官:好了,刚才你提到了Go协程调度的GMP模式,那现在有这么一个问题,你看看:如果我现在从协程中发出一个http请求,发往百度,get一些数据。请问,在这个过程中,协程是怎么调度的?

沃茨,你怎么不按套路出牌? 调度的大致流程我都说清楚了啊。

完了,大脑一片空白,你问我发起一个baidu网络请求会发生什么?好像没想过。会切换吗?

图解Go的协程调度(一),被面试官吊打后的总结

我:这个,嗯。。。啊。。。哦。。。,给我5分钟我再思考一下。(5 minutes later)不知道。。。

面试官:好了,回去等消息吧~

唉,看吧,我总是在某一个方面持续弱鸡,不得不事后复盘、学习、总结。

事后,我痛定思痛,希望下次不要再被问倒,这里将总结到的东西分享给大家,希望未来大家过关斩将,一路打通boss!

如雷贯耳的 GMP 模型是怎么来的?

一个程序在执行的时候,我们能get到它的本质,其实就是一个打工者的无限循环。如下:

图解Go的协程调度(一),被面试官吊打后的总结

后来,操作系统支持了线程机制,一条打工人流水线就变成多条打工人流水线了:

图解Go的协程调度(一),被面试官吊打后的总结

打工人需要G0工头帮忙找活干,那活怎么找呢?简单!活都放到池子里,要活干的让他们 G0 工头来取。

图解Go的协程调度(一),被面试官吊打后的总结

每次 G0 来取活都要加锁,防止活被取重了,但是这个效率有点低啊,要不每个循环给分配一个 P?活在被创建的时候可以随机丢到一个 P 池子里。

图解Go的协程调度(一),被面试官吊打后的总结

但是活总有简单和复杂的,不会那么平均的,很快一个 P 就闲的蛋疼了。工头一看,这不是事啊,搞个公共池子,P里没有了就来公共池子里取,嘿嘿,妈妈再也不用担心你没活干了。

图解Go的协程调度(一),被面试官吊打后的总结

不知道大家看出来没有,这里的 G 其实就是那么多的活被拆出来的部分,这么多 G 其实对应的就是逻辑的分布式

很多分布式的逻辑其实可以并行执行,至于这个逻辑在哪个打工人线程上执行,并不需要那么关心。

图解Go的协程调度(一),被面试官吊打后的总结

活怎么分配的逻辑,其实就是协程调度的逻辑。

工头找活干逻辑

这时候,工头 G0 闪亮登场~ 由它来找活。

其实这个G0工头找活干的代码非常简单,就是下面这段:

# 找一个可以干的活(gp)
gp := findRunnable() 
# 干活
execute(gp)  

找活干的逻辑和单线程执行时候的 one loop per thread 非常类似。流程大概是:工头 G0 不停的 loop

有需要执行的定时器活么?

图解Go的协程调度(一),被面试官吊打后的总结

咱们 P 池子里有活干么?如果没有,我老人家(G0)就去公共池子里取一些过来:

图解Go的协程调度(一),被面试官吊打后的总结

再看看有没有七大姑八大姨梢信过来,让帮忙做点事的?这年头,经常有新的连接或者连接上的请求过来,咱们要抓紧了:

图解Go的协程调度(一),被面试官吊打后的总结

也没有吗?那我可要发扬互帮互助的集体主义精神,主动承担灰色地带的任务咯?

图解Go的协程调度(一),被面试官吊打后的总结

张一鸣:哦哟,小伙子,你字节范不错哦!年低给你打M+!

图解Go的协程调度(一),被面试官吊打后的总结

什么?连别人那边的任务都没有?那有点不好意思啊,那么去摸个10ms的鱼不过分吧?

图解Go的协程调度(一),被面试官吊打后的总结

系统自动运行总会出问题的

大家都知道,一个好的系统,不可能自己完全运行流畅无阻,总得需要 G0 工头出面帮解决一些卡点

现在,第一个问题就来了:G1 兄,你占着这个茅坑拉屎拉了1个小时33分钟了,是不是让一下?G2 兄他快憋不住了,G3 兄差点拉裤裆里了。

图解Go的协程调度(一),被面试官吊打后的总结

G1 兄一听,觉得不好意思,行行,正好我也马上要去问百度一点事情,你就让G2 兄进来吧,瞧瞧,脸都憋红了。下次,早点和 你 G1 叔说嘛~

于是 G0 就领着憋得不行的 G2 进来:

图解Go的协程调度(一),被面试官吊打后的总结

G2 是个好小伙子,就怕麻烦到别人(小日子:是在说我吗?),所以述福了1分钟就主动让贤,退了出来,G0 工头顺势让 G3 上去执行:

图解Go的协程调度(一),被面试官吊打后的总结

这里的协作契机就有很多,所谓协程,就是大家一起协作。怎么协可是很有讲究的。这也是我面试被考倒的地方。于是我特意总结了,哪些情况下,协程G会需要中断手头的活,退出

调度时机

golang的协作式调度和 thread 的抢占式调度不同,需要一些用户事件来作为调度时机。thread 是抢来抢去,协程是协来协去。

协去场景1:

  • 主动调度

如果一个 G 觉得自己运行了很长时间,有点不大好意思,就会主动调用 runtime.Gosched。这个函数调用后,会从当前g切换到工头协程g0,这叫主动让贤,也是我们 G2 同学做得事情,鼓掌~ 尧舜禹看了都感动不已。

协去场景2:

  • 被动调度

大部分情况下,G 做了一些事情,需要等待一段时间,这时候就会触发被动调度。G被动被协走的情况有很多,如

  • sleep(写文章写得头晕,去睡一下不过分吧? 这里得场子就交给工头兄帮忙看一下了)
  • 加锁等,atomic/mutex(一时半会也锁不上,嘿嘿,先happy去,然后等通知)
  • channel 阻塞(哎,怎么就我在这等活,惨,趁着活还没来,先眯一会)
  • 网络IO(去问百度一个事情,百度返回之前我不能干等着吧?先干点别得)
  • 系统调用(我这活一时半会干不完,要等上面来信,先干点别得)
  • 执行GC(门前雪得扫一扫了,不能天天吃喝玩乐呀)

但总有一些 G 不识抬举的,都执行半天了也不知道下来,不自觉!

此时呢,工头就得想点办法,强制打断这家伙。比如:

  1. 每次切换函数得时候,判断一下你丫得是不是占了太长时间得茅坑了?

这时候,如果有长期摸鱼得,就被抓个正着。

图解Go的协程调度(一),被面试官吊打后的总结

也有一些 G 想利用漏洞,唉?我就不切换函数,就死命跑循环,来打我啊?

图解Go的协程调度(一),被面试官吊打后的总结

为此 go1.14 引入了基于信号的强制抢占策略。主要就是在信号处理函数中,做异步抢占。

好了,漏洞都堵上了,结束。

现在大家都其乐融融,一起过上了快乐得日子。