无缓冲 channel 本质是同步通信,需收发双方同时就绪;缓冲 channel 通过环形数组解耦速率,容量应依峰值速率与延迟权衡,误用会导致死锁或 OOM。

无缓冲 channel 的本质是同步通信
声明 make(chan int) 创建的是无缓冲 channel,它不存储数据,每次 send 必须等待对应 goroutine 执行 receive,反之亦然。这本质上是一种同步点(synchronization point),类似互斥锁的“握手”机制。
常见错误现象:fatal error: all goroutines are asleep - deadlock —— 一个 goroutine 单方面发数据却没人收,或单方面收数据却没人发。
- 适合场景:任务交接、信号通知(如
donechannel)、需要严格时序控制的协作逻辑 - 性能影响:零内存开销,但阻塞等待会暂停 goroutine 调度,若配对操作耗时长,可能拖慢整体吞吐
- 不要用它来“暂存”数据;它不是队列,是通道上的“红绿灯”
缓冲 channel 的容量不是越大越好
声明 make(chan int, 10) 创建缓冲为 10 的 channel,它内部维护一个环形数组,最多缓存 10 个元素。发送方在缓冲未满时不阻塞,接收方在缓冲非空时不阻塞。
容易踩的坑:len(ch) 返回当前已缓存元素数,cap(ch) 返回缓冲容量 —— 这两个值和 channel 是否阻塞强相关,但很多人误以为 len(ch) == 0 就代表“空闲可发”,忽略了接收端是否跟得上。
- 典型使用场景:解耦生产者与消费者速率差异(如日志采集器批量写入磁盘)
- 选容量的关键依据是「峰值写入速率 × 可接受的最大延迟」,而不是拍脑袋填 1000
- 缓冲过大会掩盖背压问题:下游卡住时数据越积越多,OOM 风险上升;缓冲过小则频繁阻塞,失去缓冲意义
如何判断该用缓冲还是无缓冲
核心看通信双方是否存在天然的“节奏错位”。如果 sender 和 receiver 总是成对出现、逻辑上必须同步完成(比如等一个结果再继续),就用无缓冲;如果 sender 是高频事件流、receiver 是低频批处理,就必须加缓冲。
一个实用判断法:把 channel 想成 API 接口。如果调用方期望“发完就返回”,不关心对方是否立刻处理,那就是缓冲 channel;如果调用方明确要“等对方确认才返回”,就是无缓冲。
- HTTP handler 向后台任务队列投递请求 → 缓冲 channel(避免阻塞响应)
- goroutine 等待另一个 goroutine 初始化完成 → 无缓冲 channel(
done := make(chan struct{})) - 多个 goroutine 向同一 channel 发送状态,主 goroutine 统一收集 → 缓冲 channel + 合理 cap,防丢失
缓冲 channel 的关闭与 range 行为要小心
关闭缓冲 channel 后,未读数据仍可被接收,直到全部取完才触发 range 结束。这点和无缓冲不同——无缓冲 channel 关闭后立即无法接收(除非之前已阻塞在 receive 上)。
ch := make(chan int, 2)
ch <- 1
ch <- 2
close(ch)
for v := range ch {
fmt.Println(v) // 输出 1, 2,然后退出
}常见陷阱:关闭后还试图发送,会 panic;关闭前未消费完缓冲数据,range 会漏掉部分值;用 select 配合 default 读缓冲 channel 时,可能跳过尚未到达的数据。
真正难的不是设多少缓冲,而是谁负责关闭、何时关闭、关闭后接收端是否已准备好处理剩余数据 —— 这些边界比容量数字重要得多。











