带缓冲 channel 仍会阻塞,因其仅在缓冲区未满时允许非阻塞发送;一旦满(qcount == dataqsiz),发送 goroutine 会被挂入 sendq 队列等待,直到有空位或接收者唤醒。

为什么带缓冲 channel 还会阻塞
带缓冲 channel 不是“永不阻塞”的银弹。它只在缓冲区有空位时允许发送不等待;一旦缓冲区满(len(ch) == cap(ch)),ch 就会像无缓冲 channel 一样阻塞,直到有接收者腾出空间。常见误判是以为 <code>make(chan int, 1) 就能“随便发”,但若只发不收、或接收太慢,第二次发送必然卡住。
用 select 实现非阻塞发送
当不能确定接收方是否就绪,又不想 goroutine 停在 ch 上,必须用 <code>select 配 default 分支。这是唯一安全的“试探性发送”方式:
select {
case ch <- v:
// 发送成功
default:
// 缓冲已满或接收方未就绪,立即返回,不阻塞
}- 没有
default的select在所有case不可执行时仍会阻塞 - 不要在
default里重试发送——这会变成忙等,消耗 CPU - 适合日志、监控等“尽力而为”场景,丢数据可接受
缓冲大小设多少才够用
缓冲容量不是越大越好,要结合生产/消费速率差和内存约束来定:
-
make(chan int, 1):仅适用于“最多积压 1 个任务”的简单信号通知(如健康检查响应) -
make(chan []byte, 16):适合 I/O 批处理,比如每次读取固定大小 buffer 后暂存 - 动态扩容不可行——channel 容量在
make时固定,后续无法调整 - 若预估峰值积压量为 N,建议设为
cap = N + 1,留一个余量防边界抖动
goroutine 退出前必须清空或关闭 channel
如果 sender goroutine 在退出前没处理完缓冲区里的数据,且 receiver 已退出或不再读取,这些值就永远卡在 channel 里。更糟的是,若 receiver 用 for range ch 等待,它永远不会退出——因为 channel 没被关闭。
立即学习“go语言免费学习笔记(深入)”;
- sender 应在所有发送完成后调用
close(ch),receiver 才能自然退出range - 不要向已关闭的 channel 发送,会 panic;但可以继续接收,直到所有缓存值被取完
- 若 sender 和 receiver 生命周期不对等,考虑用
context.WithTimeout包裹接收逻辑,避免无限等待残留数据
缓冲 channel 的“缓冲”只是延迟阻塞,不是消除阻塞。真正决定是否卡住的,永远是生产与消费之间的节奏差和生命周期管理。


















