<p>空 select{} 会导致死锁 panic,因 goroutine 永久休眠;所有 goroutine 卡住时运行时报 fatal error: all goroutines are asleep - deadlock!;应改用 sync.WaitGroup 或关闭的 done 通道等待。</p>

空 select{} 会直接 panic 死锁
写 select{} 不是“暂停一下”,而是让当前 goroutine 永久休眠。只要所有 goroutine 都卡在这儿,运行时立刻报 fatal error: all goroutines are asleep - deadlock!。
- 编译期不拦,运行期崩溃——这是典型的“看着合法、跑着爆炸”陷阱
- 想等子 goroutine 结束?用
sync.WaitGroup或发信号到关闭的done通道更稳妥 - 真要阻塞等待某个事件?写成
select { case ,其中 <code>done是由其他 goroutine 关闭的 channel
default 不是超时,是“非阻塞试探”
default 分支不会启动任何计时器,它只表示“此刻没通道就绪,别等了,马上执行我”。把它当超时用,等于在高并发下主动制造 CPU 空转。
- 适合做快速探测:比如检查
ch是否有数据可读,又不想阻塞当前逻辑 - 误用场景:循环里写
select { default: doWork() },结果doWork()被高频调用,CPU 拉满 - 真要超时:必须用可接收的定时通道,例如
或更安全的 <code>
多个 case 同时就绪时,执行顺序完全随机
哪怕你把退出信号 quitCh 写在第一个 case,只要它和数据通道 dataCh 在 select 判定瞬间都已就绪,Go 就可能挑后者执行。这不是 bug,是防饥饿的刻意设计。
- 不能靠书写顺序控制优先级;也不能靠发送时间先后判断哪个先被选中
- 需要严格优先级(如先响应取消)?拆成嵌套
select,或用if select { case - 调试时多跑几次,看输出是否跳变——跳变就是被随机调度了
case 右边的函数会在 select 开始前求值
写 case ,不是等到超时才调 <code>expensiveFunc(),而是在进入 select 的那一刹那,就执行它。如果它内部有 http.Get 或 time.Sleep,整个 goroutine 就卡住不动了。
- 耗时计算必须提到
select外:先算好timeout := expensiveFunc(),再写case - 高频循环中慎用
time.After:每次都会新建 timer,不触发就不回收,内存泄漏风险高 - 推荐统一走
context.WithTimeout,让取消信号自然流入http.Client.Do、sql.DB.QueryContext等 API
实际写的时候,最容易被忽略的是:case 表达式求值时机、nil channel 的静默失效、以及随机调度带来的逻辑不确定性。这些不是边缘情况,而是日常并发代码里最常引发隐性 bug 的点。


















