recover必须在可能panic的goroutine内部defer中直接调用,且需检查返回值处理;它无法捕获其他goroutine的panic,也不能替代常规error处理。

recover 必须在 defer 中调用,且只能捕获当前 goroutine 的 panic
Go 的 recover 不是全局异常处理器,它只对**同一 goroutine 中、同一 defer 链内**发生的 panic 有效。匿名协程(go func() { ... }())是独立的 goroutine,主 goroutine 的 defer 完全无法捕获它的 panic —— 这是最常见的误用起点。
所以规范写法的第一铁律:每个可能 panic 的匿名协程,都必须在内部自己 setup defer + recover。
- 错误写法:
defer recover()放在主函数里,指望它拦住子 goroutine 的 panic → 完全无效 - 正确位置:panic 可能发生的 goroutine 内部,且必须在
defer中调用recover() - 注意:
recover()必须紧跟在defer后,不能包在另一个函数里再调用(比如defer func() { recover() }()是 OK 的,但defer doRecover()且doRecover里调recover()就失效 —— 因为执行时机和栈帧不匹配)
recover 后要显式处理 err,不能只调用不检查返回值
recover() 返回 interface{},仅当当前 goroutine 正处于 panic 中时才非 nil;否则返回 nil。很多同学写了 defer recover() 就以为万事大吉,其实什么都没捕获到,panic 仍会向上传播并终止该 goroutine(日志都不留一条)。
必须显式判断返回值,并做有意义的处理:
- 记录日志(至少包含 panic 值和堆栈),推荐用
debug.PrintStack()或runtime/debug.Stack() - 避免裸
panic(v)二次 panic;如需重抛,应封装为新错误或带上下文再抛 - 不要忽略返回值:写成
if r := recover(); r != nil { ... },而不是recover(); // 忽略返回值
示例片段:
go func() {
defer func() {
if r := recover(); r != nil {
log.Printf("panic in worker: %v", r)
debug.PrintStack()
}
}()
// 可能 panic 的业务逻辑
riskyOperation()
}()
recover 无法捕获 runtime 错误(如 nil pointer dereference)的“全部上下文”
虽然 recover 能截住 panic,但它拿不到完整的 panic 堆栈(尤其跨函数调用链较深时,debug.Stack() 默认只输出当前 goroutine 的栈,且不包含 panic 发生点的精确文件行号)。更麻烦的是:像 nil pointer dereference、slice bounds out of range 这类 runtime panic,在 recover 后仅能得到类似 runtime error: invalid memory address or nil pointer dereference 的字符串,没有原始调用路径。
- 建议搭配
runtime/debug.Stack()(比debug.PrintStack()更可控,可捕获并返回字节流) - 若需精准定位,应在关键入口加防御性检查(如
if x == nil { return errors.New("x is nil") }),把 panic 转为可控 error - 注意:在 defer 函数中调
runtime.Caller(0)拿到的是 defer 所在函数的 PC,不是 panic 点 —— 所以别试图靠 Caller 推断 panic 位置
不要用 recover 替代错误处理,尤其在 IO/网络/数据库场景
recover 是兜底机制,不是错误处理流程。在 HTTP handler、数据库查询、文件读写等明确可能失败的场景,应该用 error 返回值 + if err != nil 显式判断。滥用 recover 会导致:
- 掩盖真实错误类型(比如把
io.EOF和connection reset都吞成同一个 panic 字符串) - 丢失错误上下文(如 SQL query string、HTTP 请求 ID、用户 ID)
- 性能开销:panic/recover 比普通 error 分支慢 1–2 个数量级
- 与中间件/框架不兼容(例如 Gin 的
AbortWithStatusJSON依赖正常 error 流程)
真正适合 recover 的场景很窄:第三方库内部 panic(你无法改源码)、极少数不可预知的逻辑崩溃(如反射调用失败)、或需要保证 goroutine 不退出的长期 worker。
复杂点在于:recover 的作用域、生效时机、与 runtime 错误的边界都很微妙。很多人写了一堆 defer recover 却发现日志没打、panic 还是崩了——往往是因为没意识到 goroutine 隔离性,或 recover 调用位置错了。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











