select 本身就能监听多个 channel,不需要额外“使用”技巧:Go 的 select 语句原生支持同时等待多个 case 中的 channel 操作。
Go 的 select 语句原生支持同时等待多个 case 中的 channel 操作( 或 send),只要任一 channel 准备就绪,对应分支就会执行。它不是“监听多个 channel 的工具”,它就是 Go 并发协调的核心控制流语句。
常见误解是以为要“包装”或“组合” channel 才能用 select —— 实际上直接写多个 case 即可:
select {
case msg := <-ch1:
fmt.Println("received from ch1:", msg)
case msg := <-ch2:
fmt.Println("received from ch2:", msg)
case ch3 <- "hello":
fmt.Println("sent to ch3")
default:
fmt.Println("no channel ready")
}没有 default 的 select 在所有 case 都未就绪时会**永久阻塞**;加上 default 则变成**非阻塞轮询**。这不是“必须”,而是语义选择:
default
default
case 替代 default
注意:default 分支哪怕只有一行空语句,也会让整个 select 变成零等待——容易误以为“没生效”,其实是立刻跳进了 default。
当两个或更多 case 同时可执行(比如多个 buffer channel 都有数据、或多个 send 操作的目标 channel 都有空位),Go 运行时会**伪随机选择一个**,不保证顺序、不按书写顺序、也不按 channel 地址大小。
这意味着:

select + default 或拆成多次尝试select 对 nil channel 和已关闭的 channel 行为完全不同,极易出错:
case :该 case **永远阻塞**(相当于从 nil channel 读)case :立即返回零值,且永不阻塞(关闭的 channel 可无限读)case nilChan :该 case **永远阻塞**(向 nil channel 写)case closedChan :**panic: send on closed channel**所以动态启用/禁用某个 channel 分支时,别设为 nil,而应改用带 default 的结构或临时换 channel 变量。
实际并发控制中,最易被忽略的是:select 不是事件循环,它只做一次决策。需要持续响应 channel 事件,必须把它包在 for 里;而每次循环都重新评估所有 channel 状态——这个“重评估开销”在高频率 channel 活动下可能影响性能,但通常远小于手动轮询或反射方案。