Golang goroutine无法调度问题求助:死循环引发执行异常
问题根源:Go用户态调度器的调度触发机制
这是个非常典型的Go调度器行为问题,我来给你拆解清楚:
核心原因:无函数调用的死循环会霸占逻辑处理器(P)
Go采用的是M-P-G调度模型:
- M:操作系统线程(OS Thread)
- P:逻辑处理器,它是连接M和G的桥梁,每个P都绑定了一个本地goroutine队列,只有和P绑定的M才能执行goroutine
- G:你的打印goroutine、死循环goroutine都属于G
Go的调度器是用户态调度,它需要特定触发条件才会切换goroutine,比如:
- 函数调用(尤其是涉及runtime的函数)
- 系统调用(比如IO操作)
- goroutine时间片耗尽(默认10ms左右)
但你写的for {}这种死循环,里面没有任何函数调用,也不会触发系统调用,更不会主动让出CPU。当这个死循环goroutine被调度到某个P上执行时,它会一直霸占着M和P,调度器完全没有机会介入切换到其他goroutine(也就是你那个打印i的G)。
为什么i会停在某个随机数值?
程序启动初期,调度器会把两个goroutine(打印i的和死循环的)都放到P的队列里,调度器会交替调度它们执行。但当死循环的G被选中执行后,它就再也不会让出CPU了,所以打印i的G就再也得不到运行机会,终端也就停在了最后一次打印的i值(这个数值是随机的,取决于调度器切换的时机)。
加了fmt.Println("ABC")就正常的原因
fmt.Println内部最终会调用系统调用(比如向标准输出写数据的write系统调用)。当goroutine进入系统调用时,Go调度器会自动把当前的P和M解绑,然后把P分配给其他空闲的M,这样你的打印goroutine就能被其他M+P组合调度执行了。当系统调用完成后,死循环的G会重新尝试获取P,这样两个goroutine就能交替被调度,终端也就会交替输出ABC和i了。
怎么解决这个问题?
如果你需要保留死循环但又想让其他goroutine正常运行,可以在死循环里主动让出CPU:
for { runtime.Gosched() // 主动告诉调度器:我先让一让,你调度其他goroutine吧 }
或者避免这种无任何函数调用的空死循环,尽量在循环里加入一些会触发调度的操作(比如IO、sleep、或者调用runtime相关函数)。
内容的提问来源于stack exchange,提问作者Mann King
相关产品推荐
相关产品推荐

