缓冲 channel 通过 make(chan t, capacity) 创建,capacity 为编译期常量且 ≥ 0;capacity > 0 时启用缓冲,发送阻塞当 len(ch) == capacity,接收阻塞当 len(ch) == 0;适用于解耦生产/消费节奏,如日志采集与任务队列,但需避免过大缓冲导致积压与 oom。

缓冲 channel 的创建和基本行为
make(chan T, capacity) 是唯一创建缓冲 channel 的方式,第二个参数必须是编译期常量且 ≥ 0。capacity 为 0 就是无缓冲 channel;大于 0 才真正启用缓冲。
- 缓冲 channel 发送不阻塞的前提是:当前队列长度
- 接收操作只有在队列为空时才阻塞
- 不要误以为“有缓冲就永远不会阻塞”——满的时候
send仍会挂起,空的时候recv也一样
比如:ch := make(chan int, 2),连续两次 ch 、<code>ch 成功,第三次就会卡住,除非有人在另一端 <code>。
什么时候该用缓冲 channel 而不是无缓冲
核心判断点:是否需要解耦「发送方节奏」和「接收方节奏」。典型场景是生产者可能短时爆发写入,但消费者处理较慢,又不想丢数据或加锁。
- 日志采集:日志 goroutine 频繁
ch ,后台 flush goroutine 定期批量写磁盘 - 任务队列:HTTP handler 把请求塞进
jobCh后立刻返回,worker goroutine 慢慢消费 - 注意:缓冲大小不是越大越好。设成 1000 但消费者每秒只处理 10 条,内存积压、延迟升高、OOM 风险都来了
常见错误是拿缓冲 channel 当“万能队列”用,却没配监控——一旦 len(ch) 持续接近 capacity,说明下游瓶颈已出现。
读写操作的阻塞逻辑和超时陷阱
缓冲 channel 的阻塞行为取决于当前队列状态,但很多人忽略 select + timeout 组合下的真实表现。
- 单独
在空时阻塞;<code>ch 在满时阻塞 - 用
select做非阻塞尝试时,default分支会立即执行,但容易掩盖背压信号 - 更安全的做法是带超时的
select:
select {
case ch <p>别直接用 <code>len(ch)</code> 判断是否可写——它只是快照值,竞争下不可靠;也别在循环里反复 <code>len(ch) == cap(ch)</code> 自旋,浪费 CPU。</p><h3>关闭缓冲 channel 后的接收行为</h3><p>关闭后,已缓存的数据仍可正常接收,直到全部取完;之后再读会得到零值 + <code>false</code>。</p>
- 关闭前务必确保所有 sender 已完成,否则会 panic:
send on closed channel - 接收方不能仅靠
ok判断“是否还有数据”,因为缓存清空前ok一直是 true - 典型模式是 sender 关闭 channel,receiver 用
for range ch安全遍历剩余内容
容易踩的坑:在多个 goroutine 并发向同一缓冲 channel 发送时,没人负责关闭,或由 receiver 错误地调用 close(ch)(Go 禁止 receiver 关闭 channel)。
缓冲 channel 的关键不在“有没有缓”,而在“谁控制生命周期、谁承担背压、怎么观测积压”。capacity 设多少,得看你的 P99 处理延迟和容忍的内存开销,不是拍脑袋定的。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











