没 default 的 select 会永久阻塞,除非有 channel 就绪;time.after 不应在循环中直接使用,否则导致 timer 泄漏。

没 default 的 select 会永久阻塞,除非有 channel 就绪
这是最常导致 goroutine “假死”的原因。你写了一个 select,但所有 case 对应的 channel 都没数据、也没人往里写,又没加 default,那这个 goroutine 就彻底卡住——不是慢,是永眠。
- 常见现象:程序跑着跑着不输出、不响应、CPU 为 0,pprof 看到 goroutine 停在
select上 - 调试技巧:在每个
case开头加log.Println("entering case X"),如果一行都不打,说明根本没进任何分支 - 安全做法:除非你**明确知道至少一个 channel 必定会在合理时间内就绪**,否则务必加
default或超时机制 - 注意:
nilchannel 的case永远不会就绪,等价于该分支“不存在”,别误以为它只是慢一点
time.After 别在循环里直接用
每次调用 time.After 都会新建一个 timer,如果超时没触发(比如 channel 一直没数据),这个 timer 就一直活着,无法被 GC 回收。
- 高频循环中滥用,几分钟后内存和定时器句柄双双暴涨
- 短生命周期操作(如单次 HTTP 请求)可用
context.WithTimeout,自动管理 cancel 和 timer 释放 - 需要手动参与
select的场景,改用time.NewTimer,并在退出前显式调用timer.Stop() - 错误写法:
for { select { case —— 每轮都造一个永不销毁的 timer
default 不是超时,是“此刻无事可做”
default 分支不启动任何计时器,它只表示:“现在所有 case 都不就绪?那就立刻执行我,别等。”
- 误当超时用,会导致逻辑失控:比如你想等 3 秒再重试,结果
default每毫秒都执行一次,重试逻辑疯狂刷屏 - 适合场景:快速探测 channel 是否有数据(
select { case x := ),或心跳保活 - 高频空转风险:
for { select { default: doWork() } }会吃满 CPU,必须配合time.Sleep或time.Tick - 真要超时,请用可接收的定时 channel:
case 或更稳妥的 <code>ctx.Done()
多个 case 同时就绪时,执行顺序是随机的
哪怕你把控制信号通道 quitCh 写在第一个 case,只要它和数据通道 dataCh 在判定瞬间都已就绪,Go 就可能挑 dataCh 先处理。
- 这不是 bug,是防饥饿的设计;测试时跑几次结果一样,多跑几十次随机性立马暴露
- 依赖顺序?别靠
select结构本身,拆成嵌套select或用if+ 单独select - 需要严格优先级(如先响应取消)?
select不是解法,该上container/heap做优先级队列 - case 右边的表达式(如函数调用)会在
select进入前求值,别把http.Get或耗时计算塞进去
select 的随机性和静默失效(如 nil channel)是最容易被忽略的两个点——它们不会报错,也不会 panic,只会让逻辑在某个时刻悄悄偏离预期。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











