本文主要记录在golang学习的过程中看到的或者自己经历的golang开发中觉得比较有意思的小故事,会持续更新
一个有趣的小细节(转载,原文在这里)
先看一段代码
package main
import (
"fmt"
"runtime"
)
func main() {
var i byte
go func() {
for i = 0; i <= 255; i++ {
}
}()
fmt.Println("Dropping mic")
// Yield execution to force executing other goroutines
runtime.Gosched()
runtime.GC()
fmt.Println("Done")
}
首先来说一下结论,这段代码会卡死,毋庸置疑。总结原因总有两点,一个是goroutine中的for循环由于byte的存储设计永远不会大于255,相当于是死循环,所以一旦进入就不会自动停止;第二个原因是runtime.GC()会调用stw暂停所有的goroutine,而本文中for的goroutine永远不会停止,所以卡死(stw只会关闭空闲的p,然后等待忙碌的p)。这个点我认为跟Gosched()关系不大,事实上这句话只关系着goroutine开始的时间,因为gc在关闭goroutine时一定会切换到goroutine,或者说它暂停了mian的goroutine自动会切换到for的goroutine,所以Gosched在这里并没有特别关键的作用。
如果我们往下进一步去改造这段代码,比如在for循环里面加一句print操作,会发现这时候程序就可以正常退出了,这个是因为当io缓冲满了需要输出到stdout时会阻塞for的goroutine,此时这个goroutine就处于空闲状态了。发现这里的本质了么,就是在gc生效陷入等待以后要想办法让for暂停下来,转给io是一个方法,调用Gosched放弃CPU也是一个办法,只要让系统认为它是空闲等待或阻塞状态即可。
然后我们回归这个问题的本质,gc触发stw机制会暂停所有空闲或等待阻塞的goroutine,等待忙碌的goroutine自动完成,或自动暂停,如果不会,则陷入卡死,除了GC意外,事实上runtime.GOMAXPROCS()也会触发stw。