go 的 select 中所有 case 表达式在进入阻塞前即全部求值,非惰性求值;若某 case 通道操作可立即完成,则该 case 被选中执行。

select 中 case 表达式在进入阻塞前就全部求值
Go 的 select 不是“惰性求值”——所有 case 后面的通道操作(如 ch 、<code>)会在进入等待前**立即执行求值**,包括函数调用、变量读取、方法调用等。这意味着即使某个 <code>case 最终没被选中,它右边的表达式也已经运行完了。
常见错误现象:select 看似“选一个可执行的分支”,但实际写成 case ch 时,<code>expensiveFunc() 每次都会被调用,哪怕 ch 正忙、最终走的是 default 分支。
- 使用场景:带副作用的发送/接收操作(如日志打印、状态更新、资源分配)
- 性能影响:无意中触发高开销函数,尤其在高频循环的
select中 - 正确做法:把有副作用的逻辑移到
case块内部,只让通道操作本身出现在case标签行
避免在 case 标签行调用含副作用的函数
这是最常踩的坑。比如下面这段代码:
select {
case ch <p><code>generateID()</code> 总是被执行,即使 <code>ch</code> 已满、最终走 <code>default</code>。ID 被生成却丢弃,可能造成 ID 泄漏或状态不一致。</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/gongju/2525" title="Go语言(Golang)1.26.0"><img
src="https://img.php.cn/upload/manual/001/589/237/6a6adeed24a4a355.png" alt="Go语言(Golang)1.26.0" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/gongju/2525" title="Go语言(Golang)1.26.0" class="overflowclass">Go语言(Golang)1.26.0</a>
<p class="overflowclass">Go语言(Golang)1.26.0版本官方下载,版本号 1.26.0,适合旧项目维护、兼容性测试和指定版本开发环境搭建。</p>
</div>
<a rel="nofollow" href="/xiazai/gongju/2525" title="Go语言(Golang)1.26.0" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
- 修复方式:把
generateID()移到case块内,确保只在真正发送时才调用 - 错误写法:
case ch —— 所有子表达式都提前算 - 安全写法:
case ch ,其中 <code>id在select外或case块内计算
nil channel 在 select 中的特殊行为:永远阻塞,但表达式仍求值
如果某个 case 使用了 nil 通道(例如 var ch chan int 未初始化),该 case 永远不会被选中——但它的右侧表达式依然会执行。
常见错误现象:程序没崩溃,但日志里发现 log.Printf("about to send") 被打印了,而 ch 实际是 nil,发送永远卡住。
- 示例:
case nilCh → <code>logAndReturn(42)照常执行并返回,然后整个case被忽略 - 兼容性注意:该行为在所有 Go 版本中一致,不是 bug,是规范定义
- 排查建议:对可能为
nil的通道,先做非空判断,或统一用default配合显式检查
多个 case 同时就绪时的伪随机选择与求值顺序无关
当多个 case 都可立即执行(如缓冲通道未满、有数据可收),Go 运行时会**伪随机选择一个**,但这个选择发生在所有 case 表达式求值完成之后。也就是说,求值顺序固定(从上到下),但执行顺序不可预测。
- 关键点:求值 ≠ 执行。所有
case右侧表达式按书写顺序求值完毕,再统一决定哪个case块体执行 - 陷阱:不要依赖
case上下文中的变量赋值顺序来控制逻辑(比如期望case a 先于 <code>case b 影响状态) - 真实影响:若
f1()和f2()都修改同一全局变量,结果取决于它们各自的求值顺序(确定),但哪个case最终被选中不确定 → 整体行为不可控
最容易被忽略的一点:很多人以为 select 是“先看哪个能走再算哪个”,其实它更像“先把所有候选动作都准备好(包括副作用),再掷骰子挑一个执行”。一旦有函数调用、方法调用、甚至短变量声明出现在 case 标签行,就已脱离你的控制节奏。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










