不知道大家在实际工作中有没有遇到过老版本 Go 调度器的坑:死循环导致程序“死机”。我去年就遇到过,并且搞出了一起 P0 事故,还写了篇弱智的找 bug 文章。
识别事故的本质,并且用一个非常简单的示例展示出来,是功力的一种体现。那次事故的原因可以简化成如下的 demo:
![](https://img-blog.csdnimg.cn/img_convert/5e9f4e249056d580a0369f07e5ac2c49.png)
我来简单解释一下上面这个程序。在主 goroutine 里,先用 GoMAXPROCS 函数拿到 CPU 的逻辑核心数 threads。这意味着 Go 进程会创建 threads 个数的 P。接着,启动了 threads 个数的 goroutine,每个 goroutine 都在执行一个无限循环,并且这个无限循环只是简单地执行 x++
。
接着,主 goroutine sleep 了 1 秒钟;最后,打印 x 的值。
你可以自己思考一下,输出会是什么?
如果你想出了答案,接着再看下面这个 demo:
![](https://img-blog.csdnimg.cn/img_convert/cc0f0ad66a7b6cee1e90a93863d62e3f.png)
我也来解释一下,在主 goroutine 里,只启动了一个 goroutine(虽然程序里用了一个 for 循环,但其实只循环了一次,完全是为了和前面的 demo 看起来更协调一些),同样执行了一个 x++
的无限 for 循环。
和前一个 demo 的不同点在于,在主 goroutine 里,我们手动执行了一次 GC;最后,打印 x 的值。
如果你能答对第一题,大概率也能答对第二题。
下面我就来揭晓答案。
其实我留了一个坑,我没说用哪个版本的 Go 来运行代码。所以,正确的答案是:
Go 版本 | demo-1 | demo-2 |
---|---|---|
1.13 | 卡死 | 卡死 |
1.14 | 0 | 0 |
这个其实就是 Go 调度器的坑了。
假设在 demo-1 中,共有 4 个 P,于是创建了 4 个 goroutine。当主 goroutine 执行 sleep 的时候,刚刚创建的 4 个 goroutine 马上就把 4 个 P 霸占了,执行死循环,而