recover必须在defer中调用才有效,直接在函数体调用恒返回nil;它仅在panic发生且defer执行时捕获当前goroutine的panic值,无法跨goroutine生效,recover后需手动处理流程而非自动回退,且不应替代error处理可预期错误。

recover 必须在 defer 中调用才有效
直接在普通函数体里写 recover() 永远返回 nil,它只在 panic 正在发生、且当前 goroutine 的 defer 链正在执行时才有意义。Go 的 panic/recover 机制不是“异常捕获”,而是“panic 中断流的定向拦截”。
常见错误是把 recover() 放在 if 判断里、或者没包在 defer 里:
func bad() {
recover() // 永远 nil,毫无作用
panic("boom")
}
正确姿势只有这一种结构:
-
defer包裹一个匿名函数或具名函数 - 该函数内部第一句就是
recover() - 必须在 panic 触发之后、goroutine 崩溃之前执行到这行
recover 只能捕获当前 goroutine 的 panic
Go 的 panic 是 goroutine 局部的,recover() 对其他 goroutine 的 panic 完全无感。这点常被忽略,尤其在起子 goroutine 后想“兜底”主流程时。
立即学习“go语言免费学习笔记(深入)”;
例如:
go func() {
panic("sub goroutine crash") // 主 goroutine 的 recover 看不见这个
}()
defer func() {
if r := recover(); r != nil {
log.Println("caught:", r) // 这里不会打印
}
}()
如果真要跨 goroutine 处理崩溃,得靠 recover() + 通道(channel)显式传递错误,或者用 sync.WaitGroup + 错误收集,不能指望一层 defer 覆盖全部。
recover 后程序不会“自动回退”,需手动控制流程
调用 recover() 成功只是让当前 goroutine 逃过崩溃,但 panic 发生点之后的代码早已跳过,函数栈也已展开完毕。你拿到的是 panic 值,不是“重放现场”。
典型误区是以为 recover 后能继续执行 panic 下面那行:
func risky() {
defer func() {
if r := recover(); r != nil {
log.Printf("recovered: %v", r)
// 这里 return 只是退出 defer 函数,不是回到 panic 那行
}
}()
doA()
panic("fail")
doB() // 这行永远不执行
}
所以实际编码中,recover 后通常要:
- 记录日志或上报错误
- 清理资源(比如关闭文件、释放锁)
- 返回错误值或设置状态标志,由上层决定是否重试/降级/退出
- 避免在 recover 块里再 panic,否则可能丢失原始错误信息
不要用 recover 处理可预期的错误
Go 社区共识:panic/recover 是为真正意外的、不可恢复的编程错误准备的(如索引越界、nil 指针解引用),不是替代 error 返回的控制流工具。
滥用 recover 的典型场景:
- HTTP handler 里对每个请求都套 defer+recover,掩盖了本该返回 400 的参数校验失败
- 数据库查询前不检查连接状态,靠 panic+recover 当重连逻辑
- 把
json.Unmarshal的语法错误也用 recover 捕获,而不是检查err != nil
这样做的后果是:错误语义丢失、调试困难、性能开销(panic 本身成本高)、掩盖真正的 bug。该返回 error 的地方,就老老实实返回。
真正需要 recover 的地方其实很少:全局 HTTP handler 的顶层兜底、长期运行的 worker goroutine 的保活、测试框架的 panic 截获——仅此而已。


















