Go 的 channel 不保证发送与接收的调度顺序,select 中 case 执行顺序亦不可预测,程序逻辑不得依赖此顺序。

channel 发送和接收的调度顺序不保证
Go 的 chan 本身不提供发送方与接收方之间的执行时序保证,尤其当多个 goroutine 同时对同一 channel 进行 send/recv 操作时,谁先被调度、谁先完成,完全由运行时调度器决定。这不是 bug,是设计使然 —— Go 明确要求程序逻辑不能依赖这种顺序。
select 语句中 case 的执行顺序不可预测
即使在同一个 select 中列出多个 case,包括对同一 channel 的 send 和 recv,Go 也不会按书写顺序尝试它们。运行时会随机打乱 case 顺序(实际是伪随机轮询),以避免饥饿和隐式优先级。
- 写成
case ch 在前、<code>case y := 在后,不代表前者一定先被检查 - 如果 channel 无缓冲且两端都就绪(比如 sender 和 receiver 都已阻塞并等待),
select会随机选择一个成功分支 - 若想强制顺序,必须拆成独立语句或用额外同步机制(如
sync.Mutex或额外 channel 控制)
缓冲 channel 不等于“顺序队列”
缓冲 channel 的 send/recv 行为看似 FIFO,但仅限于数据流动层面;它不约束 goroutine 的启动、抢占、暂停等调度行为。例如:
ch := make(chan int, 2)
go func() { ch <- 1 }()
go func() { ch <- 2 }()
go func() { fmt.Println(<-ch) }()
这段代码中,1 和 2 的入队顺序虽确定,但哪个 go 先执行、是否在 前完成写入,完全不确定。可能 panic(因读到空 channel)、也可能输出 <code>0(未初始化值)或 1/2 任意一个 —— 取决于调度时机。
真正需要顺序控制时该怎么做
靠 channel 自身无法实现跨 goroutine 的严格时序协调。可靠做法是把“顺序”显式建模为状态流转:
- 用带缓冲的 channel + 单一消费者 goroutine,把并发输入序列化为串行处理
- 用
sync.WaitGroup等待所有 sender 完成后再启动 receiver - 用两个 channel 实现 handshaking:sender 发完数据后发 signal 到
doneCh,receiver 收到 data 后才去收doneCh - 避免在 select 中混写对同一 channel 的 send 和 recv —— 极易触发竞态且行为难推理
channel 是通信机制,不是同步原语。它的“保证”只有三点:类型安全、goroutine 安全、关闭后读取返回零值。其余一切顺序假设,都是在和调度器赌运气。


















