select 默认阻塞,仅当有case立即就绪才执行;无default时全阻塞则永久休眠,nil channel使对应case失效,time.after不宜在循环中滥用,case内不可含函数调用或赋值。

select 会阻塞,除非有 case 立刻就绪
Go 的 select 不是轮询,它会挂起 goroutine,直到至少一个 case 的 channel 操作能立刻完成。没匹配到就卡住,不是“试试看”。很多人误以为它像 switch 那样顺序检查、跳过阻塞分支——实际不会。
常见错误现象:select 卡死,goroutine 无法退出;或加了 default 后逻辑被高频空转拖垮 CPU。
- 想非阻塞尝试收发?必须显式加
default分支,否则阻塞是默认行为 - 多个 channel 同时就绪时,
select随机选一个执行,不保证顺序,别依赖执行先后 - 如果所有
case都阻塞,且没default,当前 goroutine 永久休眠(可能造成泄漏)
nil channel 在 select 中永远阻塞
nil channel 是合法值,但在 select 里它不会就绪,也不会 panic,只会让对应 case 彻底失效。这是个极易被忽略的隐性 bug 来源。
使用场景:动态启用/禁用某个通信路径(比如开关日志上报),但忘了 nil channel 会让整个 select 失去该分支响应能力。
立即学习“go语言免费学习笔记(深入)”;
- 初始化 channel 前不要直接塞进
select,先判空或用指针间接控制 - 调试时遇到某个
case死活不触发,先打印它的 channel 是否为nil - 性能影响:nil channel 不消耗资源,但会让逻辑“静默失效”,比 panic 更难排查
time.After 和 context.WithTimeout 的选择陷阱
超时控制是 select 最常用场景,但 time.After 和 context.WithTimeout 行为差异很大:前者每次调用都新建 timer,后者可复用并支持取消传播。
常见错误现象:循环里反复调用 time.After(1 * time.Second),导致大量 timer 对象堆积,GC 压力陡增;或 timeout 后 context 未 cancel,下游 goroutine 继续跑。
- 短生命周期操作(如单次 HTTP 请求)用
context.WithTimeout更安全 -
time.After适合固定延迟通知,比如“5 秒后重试”,但别在热循环里重复创建 - 注意:
context.CancelFunc必须显式调用,否则 timer 不释放,context 也不真正结束
select 里不能直接给 channel 赋值或调用函数
select 的每个 case 只能是单一 channel 操作( 或 <code>ch ),不能混入赋值、函数调用、类型断言等表达式。编译器会直接报错。
错误示例:case ch —— 这会导致 <code>heavyComputation() 在进入 select 前就被执行,失去“多路复用”的意义,还可能阻塞调度。
- 所有前置计算必须放在
select外,结果存入变量再用于case - 想在收消息后立刻处理?把逻辑写在
case的花括号内,而不是塞进 channel 操作里 - channel 操作本身是原子的,但周边代码不是;别假设
case执行顺序和你写的语句顺序一致
最麻烦的其实是嵌套:比如在 case 里又开 select,容易绕晕自己。这时候不如拆成独立 goroutine,让结构更直白。










