不能。recover无法捕获关闭已关闭通道引发的panic,因其发生于普通执行流且无defer上下文;仅当panic发生在已注册defer的函数内时recover才可能生效,但工程中应优先通过原子操作等手段避免panic而非依赖recover。

recover 能捕获关闭已关闭通道的 panic 吗?
不能。直接调用 close() 一个已关闭的通道会触发 panic: close of closed channel,但这个 panic **无法被 recover() 捕获**——前提是它发生在当前 goroutine 的普通执行流中。因为 Go 运行时对这类错误的处理是同步且非 defer 友好的:panic 发生时,如果当前函数没有正在执行的 defer(或 defer 尚未轮到它执行),recover() 就没机会运行。
为什么 defer + recover 在 close panic 场景下常常失效?
常见误写是把 recover() 放在和 close() 同一层函数里,但没确保它在 panic 前已注册为 defer:
-
close(ch)一执行就立即 panic,后续语句(包括本该注册 defer 的代码)根本不会运行 - 即使写了
defer func() { recover() }(),也必须在close()之前 执行,否则 defer 根本没注册上 - 跨 goroutine 的 panic(比如在子 goroutine 中 close)更不可能被外层
recover()捕获
真正可行的防御式写法:关通道前先检查状态
Go 没有内置的「判断通道是否已关闭」函数,但可通过 select + default 配合 recover() 的间接方式实现安全关闭。不过最稳妥、零 panic 的做法是:自己管理关闭状态。
推荐方案(无 panic,无 recover):
立即学习“go语言免费学习笔记(深入)”;
var closed uint32
func safeClose(ch chan struct{}) {
if atomic.CompareAndSwapUint32(&closed, 0, 1) {
close(ch)
}
}
说明:
- 用
atomic.CompareAndSwapUint32保证「只关一次」,线程安全 - 完全规避
close of closed channelpanic,不需要 defer/recover - 适用于任何 sync-safe 场景;若通道类型含指针或结构体,注意避免竞态访问其内部数据
什么时候 recover 才可能生效?
仅当 panic 发生在你可控的、已注册 defer 的函数内部,且该函数尚未返回。例如:
func tryClose(ch chan int) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("close panic: %v", r)
}
}()
close(ch) // 此处 panic 才会被上面 defer 捕获
return
}
但这种写法本身就有问题:
- 它假设你能控制所有 close 调用点,而实际工程中常有多处并发 close
- recover 到的是字符串
"close of closed channel",无法区分是哪个通道、谁关的 - 掩盖了设计缺陷:通道生命周期本应由单一 owner 管理,而不是靠 recover 补救
真正的复杂点不在 recover 怎么写,而在于「谁该负责关闭」「关闭时机是否明确」「是否有竞态风险」——这些比加一层 recover 更值得花时间厘清。


















