Go语言循环内select的default分支运行超时类阻塞问题咨询
Go循环内select带default分支延迟响应退出信号问题解答
核心原因
你的猜测有一定相关性但不准确,导致延迟的根本原因是无阻塞忙循环+打印输出积压共同作用的结果:
- 你写的for循环中select带default分支,只要done通道没有值,循环就会全速无阻塞执行,属于典型的忙循环。2秒的运行时间内会执行数百万次循环,产生数百万行打印输出。
- 标准输出是有缓冲区的,大量打印会快速打满缓冲区,后续的
fmt.Println调用会阻塞等待缓冲区排空,同时终端/内核处理海量打印本身也需要消耗大量时间。 - 主goroutine在2秒后向done通道写入信号时,业务goroutine还在排队处理之前积压的打印任务,必须等所有已调用的打印逻辑执行完成、进入下一轮select时,才能读到done通道的信号,所以你会等很久才看到
Bye输出。
你的验证逻辑的合理性
你删除default分支的打印逻辑后程序恢复2秒退出,刚好印证了上面的结论:
- 没有打印操作的空忙循环不会产生IO阻塞,Go 1.14及以上版本的异步抢占调度机制会在10ms时间片用完后主动打断该goroutine,把CPU资源分配给主goroutine完成done信号的写入。后续业务goroutine被重新调度时,下一轮select立刻就能读到done信号,所以可以正常2秒退出。
- 你在循环中加了1秒的sleep后,每秒只会执行一次循环,不会产生大量打印积压,主goroutine写入done信号后,业务goroutine下一轮select就能立刻读到信号,所以运行时长和预期对齐。
正确写法参考
如果需要轮询执行业务逻辑同时要及时响应退出信号,不要使用带空default的忙循环,推荐使用定时器驱动的写法:
func main() { done := make(chan bool) go func() { fmt.Println("here we go") ticker := time.NewTicker(1 * time.Second) defer ticker.Stop() for { select { case <-done: fmt.Println("Bye") return case <-ticker.C: fmt.Println("this should continue to print") fmt.Println("loop continues") } } }() time.Sleep(2 * time.Second) done <- true }
该写法完全事件驱动,不会无意义占用CPU资源,同时可以毫秒级响应退出信号。
内容的提问来源于stack exchange,提问作者Daniel Arechiga
相关产品推荐
相关产品推荐

