无缓冲 channel 一写就死锁是因为它是同步通道:发送操作会阻塞,直到有协程同时执行接收操作,否则永远等待。

Go 的 channel 不是用来“学完就放一边”的语法糖,而是并发逻辑的骨架——用错一次,轻则死锁卡住,重则 panic 崩溃,且错误现场往往不报具体行号。
无缓冲 channel 为什么一写就死锁?
因为它是同步通道:ch 这一行会卡死,直到有另一个 goroutine 正好在执行 <code>。它不存数据,只做“握手”。很多人写完 <code>ch := make(chan int) 就直接发数据,主 goroutine 自己没开接收协程,结果所有 goroutine 都 asleep,报 fatal error: all goroutines are asleep - deadlock!。
- 必须配对使用:一个 goroutine 发,至少一个 goroutine 收(不能是同一个 goroutine 顺序写再读)
- 调试时加
fmt.Println("before send")和fmt.Println("after send"),如果只看到前一句,基本就是卡在发送上了 - 适合场景:goroutine 启动通知、任务完成信号、精确控制执行节奏(比如等某件事做完再继续)
有缓冲 channel 的容量设多少才合理?
缓冲大小不是越大越好,也不是随便填个 10 或 100 就行。它本质是内存 + 调度策略的权衡。
-
make(chan int, 0)和make(chan int)等价,都是无缓冲;make(chan int, 1)是最小有缓冲,能暂存 1 个值 - 缓冲区满时,
ch 仍会阻塞——所以设成 <code>100并不意味着“永远不会阻塞”,只是把阻塞点往后推了 - 常见误用:用大缓冲掩盖设计缺陷,比如本该用 worker pool 协调的任务,硬靠
make(chan job, 1000)堆着,OOM 风险上升,背压丢失 - 建议:先按业务峰值预估瞬时积压量,再加 20% 余量;若不确定,宁可小(如
1或2),配合select+default做非阻塞尝试
close() 之后还能不能读?怎么安全判断?
能读,但行为分情况:读已关闭的无缓冲 channel,立刻返回零值 + false;读已关闭的有缓冲 channel,先把缓存里剩的数据读完,读完后再返回零值 + false。直接忽略第二个返回值 ok,很容易把零值当有效数据处理。
立即学习“go语言免费学习笔记(深入)”;
- 错误写法:
v := —— 如果 ch 已关,<code>v是0(int类型),你以为任务还在跑,其实早就结束了 - 正确写法:
v, ok := ,检查 <code>ok;或更常用的是for v := range ch,自动处理关闭和退出 - 严禁对已关闭的 channel 再发数据:
ch 会 panic:<code>send on closed channel - 关闭前确保没人再往里写:通常只由 sender 关闭,receiver 绝不 close
select + channel 组合最容易掉进哪些坑?
select 看似优雅,实则是死锁和逻辑跳变的高发区。它不保证 case 执行顺序,也不保证公平性——尤其当多个 channel 都 ready 时,runtime 随机选一个。
- 永远别在
select里只写一个case ,没 <code>default也没timeout,等于裸奔式阻塞 - 想实现“超时控制”,必须搭配
time.After():case ,而不是自己 sleep 后再读 - 关闭 channel 后,对应 case 会持续就绪(返回零值+false),若不手动置
ch = nil,该分支会不断被选中,造成忙循环 - nil channel 在
select中永远不就绪——这个特性可以用来动态开关某个监听分支
channel 的难点不在语法,而在它把调度、内存、生命周期全耦合在一起。一个 make、一个 、一个 <code>close,背后全是 runtime 在切 goroutine、挪数据、管状态。写的时候多问一句:“谁发?谁收?谁关?超时怎么办?关了还读不读?”,比背十遍语法有用得多。


















