Go Channel的FIFO行为由运行时严格保证,无论缓冲区大小如何,数据收发均按入队顺序执行,无例外。

channel 的 FIFO 行为是 Go 运行时硬保证的,不是“大概率正确”,也不是依赖缓冲区大小或并发数的巧合。只要数据成功进入 channel(无论是写入缓冲区,还是直传给接收者),它在队列中的位置就固定了,后续接收一定按该顺序取出。
为什么 make(chan int, N) 不影响 FIFO 稳定性
缓冲区只是改变阻塞时机,不改写队列逻辑:hchan 内部用环形数组 + recvq/sendq 两个链表队列协同工作,所有入队、出队、唤醒都受锁保护且严格按链表顺序执行。
- 即使
N = 1000,第 999 个值也绝不会比第 1 个值先被接收 -
make(chan int, 0)(无缓冲)下,发送必须等到接收者就绪,此时“发送完成”即等于“已交付”,顺序依然牢靠 - 缓冲区满时,新
ch 会阻塞并入sendq;缓冲区空时,会阻塞并入recvq;这两个等待队列本身也是 FIFO
多 goroutine 并发写入时,FIFO 容易被误读的点
常见错误是把“goroutine 启动顺序”或“调度快慢”当成“发送顺序”。实际上,FIFO 只约束「进 channel 的次序」,不控制谁先抢到 CPU。
- 多个 goroutine 同时执行
ch 、ch ,若缓冲区未满,它们可能几乎同时完成写入 —— 但 runtime 仍逐个拷贝、逐个入环形缓冲区,顺序不变 - 如果某个 goroutine 在
ch 前加了time.Sleep,它发送的值反而会排在后面,这才是真实的“入队顺序” - 用
struct{ n int; ts time.Time }打时间戳验证,会发现接收端n总是递增,但ts可能交错 —— 这正说明 FIFO 在起作用,而非失效
close(ch) 后的接收仍严格遵循 FIFO
关闭 channel 不会打乱已有数据的顺序:缓冲区中已存的数据照常按原序被取出;所有阻塞在 recvq 中的接收者,也按入队先后被唤醒并收到零值(ok == false)。
- 关闭前已入缓冲区的
1、2、3→ 接收顺序必为1→2→3 - 若关闭时还有 2 个 goroutine 阻塞在
,第一个被唤醒的一定是最早调用的那个 - 向已关闭的
channel发送会 panic,但接收永远安全且保序
真正容易被忽略的是:FIFO 是 channel 单一实例上的行为,不跨 channel 生效;select 多 case 场景下,不同 channel 之间的接收顺序完全不可预测 —— 别拿 select { case 的输出去质疑 ch1 自身的 FIFO。











