io.Pipe仅在单写单读且读端先就位时安全,专为异步解耦和接口适配设计,适用于下游只接受io.Reader而数据尚在生成的场景;它不是缓冲区或并发通道,用错易死锁或立即返回io.EOF。

chan 的缓冲配置不是“语言学习阶段要背的概念”,而是根据通信语义和阻塞需求选的。选错缓冲类型,轻则逻辑卡死,重则 panic 或数据丢失。
无缓冲管道 make(chan int) 什么时候必须用?
当你需要两个 goroutine **严格同步**时,比如“发完才准继续”或“等我准备好再给”。典型场景是:启动 goroutine 后等待它初始化完成、信号通知、锁替代。
常见错误现象:fatal error: all goroutines are asleep - deadlock! —— 这往往是因为只写了 ch 却没配接收方,或接收方在写之后才启。
- 无缓冲通道的发送和接收必须成对出现,否则必然阻塞
- 不能用
len(ch)判断是否可读,它永远是 0(因为没缓冲) - 别把它当队列用;它本质是“握手协议”,不是存储容器
有缓冲管道 make(chan int, N) 的 N 怎么定?
N 不是越大越好,也不是凭感觉填。它代表你愿意为“未被消费的数据”承担多少内存和延迟代价。
立即学习“go语言免费学习笔记(深入)”;
使用场景:生产者速度 > 消费者速度、需解耦节奏、允许短暂积压(如日志采集暂存、批量任务排队)。
- 若 N=1,相当于“最多缓一个”,适合简单背压控制
- 若 N=1000,意味着你接受最多 1000 个未处理项驻留内存,且消费者滞后时,生产者会卡在第 1001 次写
-
cap(ch)返回 N,len(ch)返回当前已存元素数,可用于监控积压
io.Pipe() 和普通 chan 完全不是一回事
别把 io.Pipe() 当成“带缓冲的 channel”。它是内存中的流式中转站,pr 和 pw 是绑定的 reader/writer 接口,不是 channel。
容易踩的坑:
- 只调
pw.Write()不启 goroutine 读pr→ 写操作永久阻塞 - 在
pw.Close()后再调pw.Write()→ 直接 panic:send on closed pipe - 手动
close(pr)→ 错误,pw.Close()才是正确关闭方式 - 用
ioutil.WriteFile接管道输出 → 破坏流式语义,全量加载内存,大文件直接 OOM
管道关闭和 range 遍历的配合要点
for v := range ch 依赖 channel 关闭来退出,但关闭时机很关键。
- 只能由发送方(或协调者)关,接收方关会 panic
- 必须确保所有发送已完成,再
close(ch);否则range可能提前结束,漏数据 - 关闭后,
返回零值 + <code>false(有缓冲)或零值(无缓冲),但不能再写 - 多个发送 goroutine 时,用
sync.WaitGroup或context控制关闭时机,别靠 sleep 猜
真正难的不是写出 make(chan int, 10),而是当消费者崩溃、上游断流、或者中间环节 io.Copy 返回 error 时,你能立刻判断该关哪个 writer、是否要恢复 channel、要不要补发信号——这些不写进文档,只藏在每条 panic 堆栈里。


















