不能直接用 len(ch) 和 cap(ch) 判断通道是否满;因为 len() 和 cap() 返回的是非原子快照值,其间可能被其他 goroutine 修改,导致竞态条件。

直接用 len(ch) 和 cap(ch) 判断通道是否满?别这么做
很多人看到“缓冲通道有容量”,就下意识写 if len(ch) == cap(ch) { /* 满了 */ }。这看起来直观,但实际是竞态风险源——len() 和 cap() 返回的是**快照值**,不是原子状态。两个 goroutine 可能在你检查完 len(ch) == cap(ch) 后、执行 ch 前,一个往里塞、一个往外取,导致判断失效,进而触发阻塞甚至死锁。
Go 的 channel 设计本就不鼓励这种“预检+操作”两步式逻辑。它要求你把发送/接收本身当作原子决策点。
- 唯一安全的“是否满”判断,就是尝试发送并观察是否阻塞——而这必须通过
select+default实现 -
len(ch)仅适合调试或监控(比如打日志看积压),不能用于控制流 - 对 nil channel 调用
len()或cap()会 panic,而select中 nil case 会被静默忽略,更安全
用 select + default 实现非阻塞发送并检测满状态
想发数据但不想卡住?核心就是让发送操作变成“尝试型”。只要加上 default 分支,select 就不会阻塞:
select {
case ch <- v:
fmt.Println("sent")
default:
fmt.Println("channel full or closed, drop")
}这段代码执行后,只有两种可能:ch <- v 成功(说明有空位),或立刻走 default(说明缓冲区满,或 channel 已关闭,或 ch 是 nil)。
立即学习“go语言免费学习笔记(深入)”;
在 Golang 中使用 samber/hot 进行内存缓存,支持 LRU、LFU、TinyLFU、W‑TinyLFU、S3FIFO、ARC、TwoQueue、SIEVE、FIFO 等淘汰算法,提供 TTL、缓存加载器及分片功能。
- 如果
ch是无缓冲通道,default会立即触发(因为没人接收),等价于“不可发送” - 如果
ch已关闭,ch <- v会 panic,所以务必确保发送前 channel 未关闭;可在select外加if ch == nil防御 - 若需区分“满”和“关闭”,得靠额外信号(如用
sync.Once管理关闭状态,或引入donechannel 协同判断)
接收端怎么知道通道是否为空?同样不能靠 len(ch) == 0
和发送端对称:用 len(ch) == 0 判断“空”一样危险。刚读完 len() 是 0,下一纳秒另一个 goroutine 就 ch <- x 进去了,你再 <-ch 就阻塞了。
正确姿势仍是 select + default:
select {
case v := <-ch:
fmt.Println("got", v)
default:
fmt.Println("channel empty, skip")
}- 这个
default分支触发,只说明此刻无人可送、缓冲区为空——不等于“永远没数据”,只是当前不可接收 - 若配合
time.After,就能升级为带超时的接收:case v := <-ch:或case <-time.After(100 * time.Millisecond): - 注意:对已关闭且无剩余数据的 channel 执行
<-ch会立即返回零值 +false,但该行为只在成功 case 中体现;default不会告诉你 channel 是否已关,得用v, ok := <-ch单独判
高频轮询时,default 后该 time.Sleep 还是 runtime.Gosched()?
如果在一个 tight loop 里反复 select + default,CPU 会飙高。这时必须退避。选 time.Sleep 还是 runtime.Gosched(),取决于场景:
-
time.Sleep(1 * time.Millisecond)是最常用、最易理解的选择:让出 OS 时间片,降低 CPU 占用,延迟可控 -
runtime.Gosched()只让出当前 goroutine 的执行权,不保证休眠时间,适合极低延迟敏感、且确定其他 goroutine 正在处理通道的场景(比如 producer-consumer 模式中 consumer 主动让 producer 先跑) - 绝对不要用
for {}空转 —— 它吃满一个 CPU 核,且无法被调度器打断 - 如果业务允许,优先考虑事件驱动:比如用额外的 signal channel 通知“有新数据可读”,而不是消费者轮询
真正难处理的从来不是“怎么发”,而是“发不出去时要不要丢、丢多少、要不要背压反馈”。这些决策不在通道原语里,得靠上层协议设计补全。

















