关闭后的 channel 仍可读但不可写,这是 go 的明确设计;向已关闭的 channel 写数据会 panic。

关闭后的 channel 仍可读,但绝不能写 —— 这不是“还能用”,而是 Go 明确设计的通信契约。
向已关闭的 chan 写数据会 panic
只要执行 ch ,就会触发 <code>panic: send on closed channel。这不是延迟报错,是立即崩溃。
- 即使
ch是带缓冲的(如make(chan int, 10)),关闭后写入照样 panic - panic 不会被 defer 捕获,除非在 goroutine 内部用
recover()显式兜底(不推荐作为常规逻辑) - 常见踩坑:多个 goroutine 都可能触发“发送完成 → 关闭”逻辑,没做同步就直接
close(ch),结果某个 goroutine 还在写,立刻崩
从已关闭的 chan 读取:分阶段返回不同值
读行为安全,但返回值含义随状态变化:
- 关闭前已入队的数据:正常读出,
ok为true - 缓冲区已空、channel 已关闭:返回零值(如
0、""、nil),ok为false - 用
for range ch时,循环会在读完所有缓存数据 + 收到关闭信号后自动退出,无需手动检查ok
示例:ch := make(chan int, 2); ch → <code>v == 1, ok == true;再读一次 → v == 0, ok == false。
close() 只能由发送方调用,且只能调用一次
这是并发安全的底层约定,不是建议,是强制规则:
- 接收方调用
close(ch)属于未定义行为,多数情况 panic(尤其当ch是只读类型时编译不过) - 重复
close(ch)必 panic,哪怕在不同 goroutine 中 —— Go 不做幂等处理 - 对
nilchannel 调用close()同样 panic - 多生产者场景下,必须协调好谁来关(例如用
sync.Once或额外信号 channel)
为什么 range 循环比 for { select { case 更适合收尾
本质区别在于语义和控制粒度:
-
for range ch是声明式:「我只关心数据流,关了就停」,自动感知关闭并退出,代码简洁无冗余判断 - 裸
select块若没加default或超时,会在 channel 关闭后卡在case 上 —— 因为关闭后接收仍合法,只是返回零值+false,但 <code>select不自动退出 - 若用
select,必须显式配合ok判断,否则容易漏掉关闭信号,导致 goroutine 悬停
真正容易被忽略的点:关闭 channel 本身不杀死 goroutine,它只是发信号;goroutine 是否退出,取决于你用什么方式读 —— range 自动响应,手动接收必须自己处理 ok == false。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











