无缓冲 channel 主 goroutine 一发就卡死,因为它是同步通道,发送必须与接收 goroutine 实时配对握手,否则永久阻塞并触发死锁 panic。

无缓冲 channel 一写就死锁,不是 bug,是设计使然;有缓冲 channel 容量设错,不是卡住就是 OOM;close 之后还乱读,零值当有效数据用,逻辑就崩了。
无缓冲 channel 为什么主 goroutine 一发就卡死?
因为它是同步通道:ch 这行会原地阻塞,直到另一个 goroutine 正好执行 <code>。它不存数据,只做“握手”。主 goroutine 自己发完不启接收协程,所有 goroutine 都 asleep,报 <code>fatal error: all goroutines are asleep - deadlock!。
- 绝对不要在同一个 goroutine 里顺序写再读:
ch —— 这等于自己等自己 - 调试时加
fmt.Println("before send")和fmt.Println("after send"),如果只看到前一句,基本就是卡在发送上了 - 适合场景很窄:goroutine 启动通知(如
done chan struct{})、任务完成信号、精确节奏控制(比如等某件事做完再继续)
有缓冲 channel 的容量怎么设才不翻车?
make(chan int, N) 的 N 不是随便填个 10 或 1024 就行,它是背压缓冲区,本质是“允许发送者比接收者快多少”。填大了掩盖瓶颈、吃内存;填小了频繁阻塞、吞吐掉链。
- 用于 worker pool 类任务队列:按预期并发数 × 1.5~2 设,比如 8 个 worker 就配
make(chan job, 12) - 仅作信号通知(如
done chan struct{}):用无缓冲更轻量,make(chan struct{})即可 - 不确定流量时,宁可保守设小(
1或2),配合select+default做非阻塞尝试 - 查当前积压:
len(ch)是已存元素数,cap(ch)是最大容量,差值就是还能塞几个
channel 关闭后还能读吗?怎么安全判断是否关了?
能读,但行为分情况:读已关闭的无缓冲 channel,立刻返回零值 + false;读已关闭的有缓冲 channel,先把缓存里剩的数据读完,读完后再返回零值 + false。忽略 ok 返回值,很容易把 0、""、nil 当成有效数据。
立即学习“go语言免费学习笔记(深入)”;
- 错误写法:
v := —— 如果 <code>ch已关,v是0(int 类型),你以为任务还在跑,其实早就结束了 - 正确写法:
v, ok := ,检查 <code>ok == false;或更常用的是for v := range ch,自动处理关闭和退出 - 严禁对已关闭的 channel 再发数据:
ch 会 panic:<code>send on closed channel - 关闭前确保没人再往里写:通常只由 sender 关闭,receiver 绝不
close(ch)
select 监听多个 channel 时,为什么有时收不到数据?
select 是非确定性多路复用器,当多个 case 都 ready 时,它随机选一个执行。如果某个 case 永远不 ready(比如对应 channel 没人发、没关、也没设 default),整个 select 就卡住。
- 漏写
default:想做非阻塞试探却没加,select就干等,结果和单个 channel 阻塞一样 - 误关 channel 太早:比如在
select外提前close(ch),后续case 可能立即返回零值+false,但你没检查 <code>ok,逻辑就偏了 - nil channel 在
select中永远不 ready:把未初始化的var ch chan int直接丢进case ,这个分支永远不会触发 - 超时控制别硬写死:用
time.After而不是time.Sleep,否则起不到中断作用
最易被忽略的一点:channel 的生命周期管理必须和业务语义对齐。不是“开了就能用”,而是“谁创建、谁负责关;谁发、谁关;谁收、谁不关”。关早了数据丢,关晚了 goroutine 泄漏,不关则 for range 永不退出 —— 这些都不是语法错误,是设计断点。


















