前段时间在找工作,也遇到一些不错的面试题,其中有一道很常见,记录一下,里面还有一点搞不明白的:
下面两段程序的输出是什么?
第一段:
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
fmt.Println(i)
wg.Done()
}()
}
wg.Wait()
}
第二段:
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(n int) {
fmt.Println(n)
wg.Done()
}(i)
}
wg.Wait()
}
很多面试题解析里面说第一段的10个goroutine输出全部是10,我对这个结论是一直持怀疑态度的,因为输出什么,取决于那个goroutine里面代码被执行时外层i循环到哪里,经我实测,也符合我自己的想法。
但是那天那个面试官很肯定的说会全部输出一样的数,我忘记问他的理由是什么了。
关于第二段程序,乱序输出0-9,相信大家是没有异议的。总计有10^10种可能。针对第二段程序,那位面试官接着问了一个我觉得挺有水平的问题:这10^10种输出里面,肯定有一种是按顺序0-9依次输出的,能不能通过一些方法,让这段程序的输出顺序固定下来?这个问题我一时还真的抓不到要点了。。。后来在面试官不断的提点下,我才想到面试官的考点,不禁觉得这个面试官还是很有水平的。
第二段程序如何改动才能达到定序输出的效果呢?我们知道每个goroutine生成后,在P的本地G队列未满的时候,是依次加入到P的本地G队列里的,如果只有一个P可用,也就只有一个本地G队列存在,那么这些G的执行顺序其实是取决于P的G队列的顺序的,那么答案也就出来了,我们只要设置P的数量为1,即可达到定序输出的目的:
func main() {
runtime.GOMAXPROCS(1)
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(n int) {
fmt.Println(n)
wg.Done()
}(i)
}
wg.Wait()
}
不过这里我还是有一点不明白的是,9为什么是第一个被输出的?我猜大概是跟GMP调度有关的。目前还不明白,有知道的同学可以指点我一下,谢谢。
以上如有错误,欢迎指出。
有疑问加站长微信联系(非本文作者)

“每个goroutine生成后,在P的本地G队列未满的时候,是依次加入到P的本地G队列里的” 这句话中的P、G含义?
P是逻辑处理器P0 G是程序中开的协程吧?
9 为什么是第一个输出的,我之前有专门有给星球的用户写文章讲解过。
runtime2.go 中关于 p 的定义: 其中 runnext 指针决定了下一个要运行的 g,根据英文的注释大致意思是说:
所以当设置 runtime.GOMAXPROCS(1) 时,此时只有一个 P,创建的 g 依次加入 P, 当最后一个即 i==9 时,加入的最后 一个 g 将会继承当前主 goroutinue 的剩余时间片继续执行,所以会先输出 9, 之后再依次执行 P 队列中其它的 g。
个人看源码找到的答案,如有错误请立刻指出。
最后的P 是 放在 local queue 的头部的,所以第一个执行
从执行角度看的确可以看作是放在了 runq 的头部
第一个循环,如果把10改为100或者1000,会有比较大的概率出现你的预期结果。
将循环改为 1000 后,输出也是固定的,但是顺序很乱,求大佬解惑
猜测 G 队列有大小,所以出现一段有顺序的,然后另一段有顺序的.但是其中又有 77 131 78 这种就奇怪了 验证中...
加个chan控制下
是go.runtime的知识,MPG。 也看是否有启动过goroutine。第一次运行估计都是10;
掘坟 https://zhuanlan.zhihu.com/p/413218471 为后人植树 重点在runnext