Go的select在多个case同时就绪时伪随机选择,底层通过哈希打乱索引后线性扫描实现公平性,避免饿死和隐式顺序依赖;default仅在无就绪case时执行,不参与随机选择。

Go 的 select 在多个 case 同时就绪时,确实会伪随机选择一个执行——这不是巧合,而是运行时明确实现的公平性机制。
多个 channel 同时就绪时,select 怎么选?
底层不按书写顺序扫描,也不轮询,而是先对所有就绪的 case 索引做哈希打乱,再线性遍历一次选出首个匹配项。这个“打乱”让每次调度结果不可预测,但长期统计接近均匀分布。
- 哪怕你把
ch1写在第一行、ch2写在第二行,也不能保证ch1先被选中 - 加日志、改编译器版本、换 CPU 核数都可能改变命中顺序
- 这种设计是为了防止开发者隐式依赖书写顺序,从而写出饿死某个 channel 的 bug
default 会让 select 变成“非阻塞”,但它不参与随机选择
default 分支永远是兜底逻辑:只要有任何 case 就绪,default 就完全不会执行;它只在所有 case 都未就绪时才触发。
-
default不影响“多个就绪case间随机选”的行为,只是改变了“无就绪时做什么” - 常见误用:在
default里写重试逻辑,期望下一轮select就能命中某个 channel——但 goroutine 调度不可控,可能连续进几十次default - 若想实现“尝试 ch1,失败再试 ch2”,得用显式判断:
if len(ch1) > 0 { ... } else if len(ch2) > 0 { ... }(注意竞态)
怎么验证是不是真随机?别靠肉眼观察
单次运行看到总是走第一个 case,大概率只是当前环境下的调度巧合,不是顺序执行。
- 用带缓冲的 channel(如
make(chan int, 1)),提前写入值,确保收发操作立即就绪 - 避免无缓冲 channel + 单独 goroutine 发送,否则依赖启动时机,结果不可复现
- 跑 1000 次循环,统计各
case命中次数——偏差在 ±5% 内属正常,大幅倾斜才说明有隐式依赖或逻辑问题
真正容易被忽略的是:随机性只作用于“就绪集合”,而“是否就绪”本身由 channel 状态、缓冲区、接收方 goroutine 是否等待等共同决定——这些才是实际业务中更常出问题的地方。


















