协程调度顺序
本文是对卷卷面试题的学习与记录
go 关键字会被编译器转化为对runtime.newproc函数的调用。该函数的主要逻辑:先切换到系统栈,然后调用newproc1函数,分配并初始化一个新的g,再通过runqput把新的g添加到当前P的本地runq中。
1 | package main |
输出:
1 | 3 |
之所以如此,是因为 P不仅有一个本地runq,还有一个runnext字段,用来保存下次要运行的g。 newproc1中调用runqput时会用到这个runnext
调度goroutine执行时,通过runqget获取待执行的g。runqget也会对runnext特殊处理:优先调度runnext这里记录的g,再按顺序调度本地runq中记录的g
所以是3-1-2
如果创建更多的goroutine,结果也如此吗?
1 | package main |
4个goroutine输出的是 4,1,2,3
5个goroutine输出的是 5,1,2,3,4
直到257个gorutine,都是这样的规律, 即 N,1,2,….,(N-1)
当多于257个goutine时,
第258号goutine会把257号挤走,但本地runq已满,所以第257号goroutine,会和本地runq中前一半的g,一同进入到全局runq中,
先从本地runq中获取待执行的g,没有的话,再从全局runq获取。还没有的话,就去其他P那里steal一部分(一半),
所以可能会以为,先执行258号,而后是129,130…,256。最后才是全局队列中的1,2,…128,257
实际并非如此…
实际执行会发现,第1号,2号goroutine,会穿插在258,129,….258之间被执行。
这个问题与runq的排队逻辑无关,属于调度逻辑的范畴
runtime.schedule,每隔61个schedtick,就会优先尝试从全局runq中获取goroutine,这是为了避免在每个P的本地runq都很繁忙时,全局runq中的goroutine迟迟得不到调度的情况。
本地runq的排队逻辑(runnext)
全局runq,每隔61个schedtick会优先从全局runq中拿一个goroutine去执行
更多阅读
原文作者: fliter
原文链接:
https://dashen.tech/2021/12/12/协程调度顺序/版权声明: 转载请注明出处