只有发送方且仅一次能调用 close(),接收方调用或重复关闭会 panic;多发送方须用 sync.WaitGroup 协调,确保全部发送完成后再由唯一 goroutine 关闭;仅通知停止时应改用 done chan struct{}。

谁该调用 close()?只允许发送方且仅一次
关闭通道不是“谁方便谁关”,而是有明确责任归属:只有向通道写数据的 goroutine(或其协调者)才能调用 close(),接收方调用会 panic —— 编译不报错,但运行时直接崩溃:panic: close of closed channel 或 panic: close of nil channel。更危险的是,多个发送 goroutine 都觉得自己是“最后一个”,各自在循环末尾写 close(ch),结果第一个执行完就关了,其余还在发数据,立刻触发 panic: send on closed channel。
常见错误场景包括:
- 工厂函数返回
ch chan int,外部代码直接close(ch)—— 违反封装,且无法保证发送已结束 - HTTP handler 启动若干 worker goroutine 往同一
ch写日志,每个 worker 自行close(ch)—— 必 panic - 把
close(ch)放在 defer 里,但 defer 执行时其他 goroutine 仍在往ch发送 —— 时间差导致竞争
sync.WaitGroup 是多发送方场景下最稳妥的关闭协调方式
当多个 goroutine 并发向同一个通道写入(比如扇入模式),必须确保所有发送者彻底退出后,再由唯一 goroutine 执行 close()。此时 sync.WaitGroup 是 Go 官方推荐、无歧义的同步原语。
关键操作链要严格按顺序:
立即学习“go语言免费学习笔记(深入)”;
- 主 goroutine 启动前对每个发送 goroutine 调用
wg.Add(1) - 每个发送 goroutine 内部用
defer wg.Done(),确保无论正常 return 还是 panic 都能通知完成 - 主 goroutine 启一个独立 goroutine,调用
wg.Wait()后再close(ch),避免阻塞主线程 - 接收端保持
for v := range ch不变 —— 它天然等待关闭 + 消费完缓冲区
切忌把 close(ch) 放进任意一个发送 goroutine 的末尾;也别在 wg.Wait() 后立刻 close(ch)(主 goroutine 中),因为调度不可控,仍可能有未完成的发送在途中。
用 chan struct{} 做退出信号比关数据通道更轻量、更安全
如果你本意只是“通知所有 goroutine 停止工作”,而不是“告诉接收方数据发完了”,那就别碰数据通道的 close() —— 它语义重、规则严、易出错。改用专用信号通道:done := make(chan struct{})。
这种模式的核心优势:
- 发送方不
send,只在需要终止时close(done)—— 关闭零开销、不阻塞、可多次 close(其实不会 panic,但依然建议只 close 一次) - 接收方用
select { case ,一旦 <code>done关闭,分支立即就绪,无需担心缓冲区或数据残留 - 类型是
struct{}:零内存占用,语义清晰(纯信号,不传数据) - 比用
chan bool或带缓冲的chan int更可靠 —— 后者可能因缓冲未满导致信号丢失
真正容易被忽略的一点是:**关闭通道和通知 goroutine 退出,是两件不同性质的事**。前者是数据流终点声明,后者是生命周期控制。混用会导致逻辑耦合过紧、难以测试、极易翻车。
别试图检测通道是否已关闭,而是让 goroutine 主动响应退出信号
网上流传的“如何判断 channel 是否已关闭”方案,比如 select { case v := 或反复 <code>len(ch),全都不靠谱。Go 没有线程安全、无副作用的 API 能告诉你通道当前是否关闭 —— 因为这不是设计目标。
v, ok := 中的 <code>ok == false 只表示“通道已关闭且缓冲为空”,但如果缓冲里还有个合法零值(比如 0),你根本分不清是数据还是关闭信号。而 for range ch 虽然自动退出,但它依赖“关闭 + 缓冲清空”两个条件同时成立 —— 若你提前 close(ch) 但还有 goroutine 在发,就可能 panic。
所以,真正可控的做法是:用 context.Context 或 chan struct{} 让 goroutine 主动监听退出时机,收到信号后自行清理、return。数据通道本身,只要没人再往里发,不关也不会泄漏 —— GC 会回收无引用的 channel。强行关,反而容易制造 panic 和竞态。


















