recover必须在defer函数中调用,且defer需位于panic触发点的同一goroutine中、尚未返回前;否则recover返回nil,无法捕获panic。

recover必须在defer函数里调用,否则返回nil
这是最常踩的坑:直接在普通函数体里写 recover(),它永远返回 nil,起不到任何保护作用。Go只允许在被 defer 包裹的匿名函数或命名函数中调用 recover(),且该 defer 必须定义在 panic 触发点的**同一 goroutine 中、且尚未返回之前**。
常见错误写法:
func risky() {
recover() // ❌ 无效,不在 defer 内
panic("boom")
}
正确写法(必须):
func risky() {
defer func() {
if r := recover(); r != nil {
log.Printf("caught panic: %v", r)
}
}()
panic("boom") // ✅ 此时 recover 才能捕获
}
-
defer语句要放在可能 panic 的代码之前,顺序不能颠倒 - 如果函数内有多个
defer,recover()所在的那个必须是最后一个被执行的(即 LIFO 中最先注册的),否则可能被更早的defer提前“消耗”掉栈帧 - 不要试图在循环里反复注册同一个
defer func(){ recover() }()—— 它只对本轮 panic 有效,且每次注册都增加开销
每个goroutine都要独立加recover,跨协程不传递
Go 的 recover() 是 goroutine 局部的:一个 goroutine 里的 panic,只能被它自己内部的 defer+recover() 捕获。主 goroutine 没加,子 goroutine 加了,主 goroutine panic 还是会崩溃;反之,子 goroutine panic 未被捕获,整个进程仍会退出(除非主 goroutine 显式等待并忽略)。
典型场景是 HTTP handler 或定时任务启了新 goroutine:
http.HandleFunc("/process", func(w http.ResponseWriter, r *http.Request) {
go func() { // 子 goroutine
defer func() {
if r := recover(); r != nil {
log.Printf("worker panic: %v", r)
}
}()
processHeavyData() // 可能 panic
}()
})
- 主 handler 不需要为子 goroutine 的 panic 加 recover,但子 goroutine 自己必须加
- 别指望外层
defer能“罩住”子 goroutine —— 它们是隔离的执行单元 - 若需统一兜底,可封装启动函数,如
go safeGo(processHeavyData),其中safeGo内置defer+recover
recover后无法回到panic发生点,业务逻辑需主动设计容错路径
recover() 的作用只是终止栈展开、避免崩溃,并让程序继续执行 defer 函数之后的代码。它不会“回滚”或“重试”,更不会自动修复状态。比如在数据库事务中 panic,recover() 后连接可能已断、临时文件没清理、内存引用已失效。
所以核心模块保护的关键不是“拦住 panic”,而是“拦住之后还能干什么”:
- 在
recover()块里做必要清理:tx.Rollback()、file.Close()、mu.Unlock() - 明确返回错误或降级响应,而不是假装一切正常继续往下走
- 避免在
recover()后继续使用可能已损坏的对象,例如 panic 来自map assign on nil map,恢复后别再往那个 map 写 - 若 panic 源于外部输入(如 JSON 解析失败),记录原始 payload 和堆栈,便于复现
别用recover替代error,优先用error处理可预期错误
Go 社区共识是:panic/recover 仅用于真正“不可恢复”的编程错误(空指针、越界、断言失败)或极端异常(如配置严重错误导致服务无法启动)。常规业务错误——参数校验失败、DB 查询无结果、第三方 API 返回 404——必须走 error 返回路径。
滥用 recover() 会导致几个隐性问题:
- 掩盖真实缺陷:把本该修复的 bug 用 recover 吞掉,日志里只剩模糊的 “panic: invalid memory address”,没人去修源码
- 破坏调用方控制流:上游函数以为你返回了
error,结果你偷偷 recover 并静默继续,逻辑错乱 - 性能损耗:每次
defer注册+栈展开+recover 判断都有开销,高频路径上尤其明显 - 调试困难:panic 堆栈被截断,丢失关键上下文;而 error 可携带完整字段、traceID、时间戳
真正需要 recover 保护的,是那些你无法提前检查、又必须保证模块不崩的“最后一道闸门”,比如 HTTP handler 入口、消息队列消费者主循环、插件系统调用外挂代码。其他地方,老老实实 if err != nil。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











