range 遍历 channel 仅在 channel 被显式关闭后退出;未关闭时会永久阻塞等待新值,导致卡住、goroutine 泄漏等问题;发送端负责关闭,接收端不可关;关闭后可读零值,写则 panic。

range 遍历 channel 什么时候会退出
channel 上的 range 只有在 channel 被**显式关闭**后才会自然退出;如果 channel 没关,range 会一直阻塞等待新值,哪怕发送端已退出或不再发数据。
常见错误现象:range 卡住、goroutine 泄漏、程序无法结束。
- 发送端用
return或函数结束退出,但没关 channel → 接收端永远等 - 多个 goroutine 向同一 channel 发送,只关一次不够 → 关闭 panic:
send on closed channel - 接收端用
for v := range ch,但发送逻辑里混用了select+default→ 看似“不发了”,其实 channel 还开着
谁该负责关闭 channel
Go 社区共识:**发送端**负责关闭 channel,接收端绝不关 —— 因为只有发送端知道“所有数据都发完了”。
典型场景:一个 goroutine 生产数据写入 channel,主 goroutine 用 range 消费。
立即学习“go语言免费学习笔记(深入)”;
- 如果发送端是单 goroutine,发完直接
close(ch) - 如果发送端有多个 goroutine(比如 worker pool),用
sync.WaitGroup等待全部完成后再关 - 切勿在接收端检测到“没数据了”就去关 —— 你根本不知道是不是真没数据,还是只是慢
range 退出后 channel 还能用吗
能读,不能写。关闭后的 channel 仍可被多次接收,每次返回零值 + false(ok 为 false);但再往里 send 就 panic。
性能影响小,但语义上代表“流已终结”,后续任何 send 都是逻辑错误。
- 接收时建议用
v, ok := 判断是否关闭,尤其在非 range 场景下 -
range内部就是靠这个机制退出的,所以不用额外判断 - 不要依赖“从关闭 channel 读出零值”来传递业务信号 —— 零值可能是合法数据(比如
int的 0、string的 "")
替代方案:用 done channel 或 context 控制退出
当 sender 不确定何时停、或需要支持中途取消时,硬关 channel 反而容易出错。这时更适合用 context.Context 或单独的 done chan struct{}。
适用场景:超时控制、用户中断、服务 shutdown。
- 接收端用
select监听ch和ctx.Done(),收到 cancel 就退出 - sender 不关 channel,而是停止发送;接收端退出后,channel 自然被 GC(前提是无其他引用)
- 避免了“谁关、何时关、关几次”的纠结,也消除了 close panic 风险
最易被忽略的一点:关闭 channel 是个**一次性操作**,且必须由唯一可信的发送方执行。多人协作时,最好把 channel 的生命周期封装进结构体,关的操作只暴露一个方法,别让任意代码都能碰 close()。










