recover 必须在 defer 中调用才有效,直接在协程函数体里调用返回 nil;每个 goroutine 需独立配置 defer/recover,无法跨 goroutine 捕获 panic;recover 后应尽快清理资源并返回,避免状态损坏引发二次问题。

recover 必须在 defer 中调用才有效
直接在协程函数体里写 recover() 没用——它只会返回 nil,因为 panic 发生时,只有当前 goroutine 的栈上存在未被处理的 panic,而 recover() 只能在 defer 函数中、且 panic 正在传播过程中才能捕获到它。
常见错误是这样写:
go func() {
if r := recover(); r != nil { // ❌ 永远为 nil
log.Println("caught:", r)
}
panic("boom")
}()
正确做法是把 recover() 放进 defer 的匿名函数里:
-
defer必须在 panic 之前注册(即在函数开头或 panic 前调用) - 同一个 goroutine 内,
defer的执行顺序是后进先出,但只要它在 panic 前注册,就能生效 - 不要试图在外部 goroutine 里 recover 另一个 goroutine 的 panic —— Go 不支持跨 goroutine 捕获
为什么不能用全局 defer 捕获所有 goroutine panic
Go 的 panic 是 goroutine 局部的:每个 goroutine 有独立的 panic 状态,recover() 只作用于当前 goroutine。不存在“主线程兜底”这种机制。
典型误操作:
func main() {
defer func() {
if r := recover(); r != nil {
log.Fatal("global recover caught:", r) // ❌ 永远不触发
}
}()
go func() {
panic("in goroutine") // 这个 panic 不会传到 main 的 defer
}()
time.Sleep(time.Millisecond)
}
结果是程序直接崩溃,main 的 recover() 完全无感知。必须在每个可能 panic 的 goroutine 内部单独加 defer/recover。
- 每个需要容错的 goroutine 都要自己配一套
defer func() { recover() }() - 如果 goroutine 是通过封装函数启动的(比如
go worker(job)),就把defer/recover写在worker函数开头 - 别指望靠启动 goroutine 前加一个
defer来覆盖——那属于另一个栈帧
recover 后继续运行的风险与边界
recover() 成功后,当前 goroutine 会从 panic 点之后继续执行,但这不等于“安全续跑”。很多状态已经损坏,盲目继续可能引发更隐蔽的问题。
比如:
go func() {
defer func() {
if r := recover(); r != nil {
log.Printf("recovered: %v", r)
}
}()
mu.Lock()
panic("failed to process")
mu.Unlock() // ❌ 永远不会执行,锁没释放
}()
这个例子中,mu.Unlock() 被跳过,导致死锁风险。所以:
- recover 后应尽快清理资源(如显式 unlock、close channel、return)
- 避免在 defer 里 recover 后还做复杂逻辑;最好只记录日志 + return
- 不要在循环 goroutine(如
for { ... })里 recover 后 continue —— 若 panic 由状态污染引起,continue 会重复触发 - recover 不是错误处理替代品;对可预期错误(如 I/O 失败、解析失败),优先用 error 返回,而非 panic
测试 recover 是否生效的可靠方式
本地验证 recover 是否起作用,最直接的方法是让 goroutine panic 后观察程序是否退出。但要注意:主 goroutine 退出会导致整个进程终止,即使其他 goroutine 还活着。
推荐写法:
done := make(chan bool, 1)
go func() {
defer func() {
if r := recover(); r != nil {
log.Printf("panic recovered: %v", r)
}
done <p>关键点:</p>
- 用 channel 或 sync.WaitGroup 确保能观测到 goroutine 执行完毕
- 不要依赖
time.Sleep判断 —— 它不可靠,且容易掩盖 goroutine 泄漏 - 如果 recover 后没显式退出 goroutine,它会继续跑完函数剩余部分,可能产生副作用
真正难的不是写对 recover,而是判断该不该 recover —— 很多时候 panic 是设计意图(比如空指针解引用),强行 recover 反而掩盖了 bug 根源。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











