recover 必须在 defer 函数中调用且与 defer 同属一个函数作用域,否则返回 nil;它仅对当前 goroutine 有效,无法跨协程捕获 panic;recover 后函数立即 return,不执行后续代码,也不回滚已发生的副作用。

recover 不能单独用,必须和 defer 绑定在同一个函数作用域里,否则永远返回 nil。
recover 必须在 defer 函数中调用
这是最常踩的坑:直接写 recover() 永远捕获不到 panic。因为 recover 只在 panic 正在传播、且当前 goroutine 尚未退出时有效,而唯一能稳定卡在这个时间窗口的位置,就是 defer 注册的函数体内部。
- 错误写法:
func bad() { recover(); panic("x") }——recover()执行时 panic 还没发生,返回nil - 正确写法:
func good() { defer func() { if r := recover(); r != nil { /* 处理 */ } }(); panic("x") } - 注意:
defer必须在 panic 触发前注册,否则来不及生效
recover 只对当前 goroutine 生效
Go 的 panic 是 goroutine 局部的,recover 无法跨 goroutine 捕获。主线程起一个 goroutine,它 panic 了,主线程不加 recover 就会崩溃;反过来,goroutine 自己不设 recover,它的 panic 会直接终结整个程序。
- 每个可能 panic 的 goroutine 都得配独立的
defer+recover块 - 常见场景:HTTP handler、定时任务、消息消费协程 —— 这些都该包裹一层
defer func() { recover() }() - 别指望在 main 函数里包一次就能兜住所有子 goroutine 的 panic
recover 后函数立即 return,后续代码不执行
recover 不是“继续执行”,而是终止当前函数的剩余逻辑,把控制权交还给上层调用者。很多人误以为 recover 能让 panic 后的代码继续跑,结果发现 fmt.Println("never printed") 真的永远不会打印。
- panic 发生点之后的所有语句(包括同层的其他 defer)都会被跳过
- 已执行的副作用(如文件写入、channel 发送、map 修改)无法回滚,recover 不提供事务能力
- 若需降级逻辑,得靠显式 error 返回 + if 判断,而不是依赖 recover 后续分支
获取 panic 堆栈需要 runtime/debug.Stack()
单纯 recover() 只返回 panic 传入的参数(比如 panic("db timeout") 中的字符串),没有行号、文件、调用链。线上排障时这远远不够。
- 必须搭配
import "runtime/debug"和debug.Stack() - 典型用法:
log.Printf("panic: %v\n%s", r, debug.Stack()) - 注意:debug.Stack() 返回的是 []byte,需转 string 或直接写入日志器
- 高频 panic 场景下,避免无节制打全栈——可考虑采样或只记录关键帧
真正难的不是写对那几行 defer+recover,而是判断哪里该用、哪里不该用。第三方库空指针、并发写 map、JSON 解析错位……这些 runtime panic 必须兜底;但业务校验失败、参数缺失、数据库查不到——这些该走 error 返回,强行 panic+recover 只会让逻辑变脆、堆栈变脏。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











