recover必须在defer函数体内调用、panic前注册、且仅对当前goroutine有效;裸调、panic后调用、跨协程或状态已损坏时均无效。

recover 不是 try-catch,它只在特定条件下起作用:必须在 defer 函数体内调用、必须在 panic 触发前注册、且仅对当前 goroutine 有效。绝大多数写错的 recover 都是因为没满足这三点中的某一个。
recover 必须写在 defer 函数体里,不能裸调
直接在函数体里写 recover() 永远返回 nil,因为此时没有正在传播的 panic。它只在 panic 正在展开栈、且当前 goroutine 尚未退出时才可能捕获到东西。
常见错误写法:
-
func f() { recover(); panic("x") }——recover()在 panic 前执行,无 panic 可捕获 -
func f() { panic("x"); recover() }—— panic 后函数已终止,recover()根本不会执行
正确写法只有一种模式:
立即学习“go语言免费学习笔记(深入)”;
func f() {
defer func() {
if r := recover(); r != nil {
// 处理 panic
}
}()
panic("x")
}
defer 必须在 panic 之前注册,顺序不能颠倒
defer 是注册动作,不是立即执行。如果写成 panic(); defer func() { recover() }(),这个 defer 根本来不及进 defer 链,panic 就已经展开栈并退出了。
关键点:
-
defer语句必须出现在panic调用之前 - 多个
defer按后进先出(LIFO)执行,recover应该放在最内层(即最后一个注册的defer)才能捕获到 panic - 如果写了两个带
recover的defer,只有第一个(即最后注册的那个)能真正捕获到 panic,后面的recover()会拿到nil
recover 无法跨 goroutine 捕获 panic
主线程里加了 defer + recover,对 go func() { panic("x") }() 完全无效。子 goroutine panic 后静默退出,主 goroutine 不知情,也不会触发任何 recover。
这意味着:
- HTTP handler 中起 goroutine 做异步任务?每个 goroutine 内部必须自己加
defer func() { recover() }() - 封装
safeGo工具函数时,必须确保传入函数内部已自带 recover,否则只是把崩溃从显式变成隐蔽 - 不要指望中间件或全局 init 里的 recover 能兜住所有协程的 panic
recover 后不能“继续执行原逻辑”,状态大概率已损坏
recover 成功后,函数会立即 return,panic 发生点之后的代码永远不会运行。更重要的是:它不回滚任何副作用。
比如:
-
arr[20] = 88导致 panic → 数组可能部分写入,但长度仍是 10,索引 20 无效 - 已向 channel 发送数据、已修改 map、已写入文件 → 这些操作不会被撤销
- 在
recover分支里再调用依赖该状态的业务代码(如重试 DB 写入),很可能引发二次 panic 或数据不一致
合理做法是:recover 后只做三件事:记录堆栈 + 返回降级响应(如 HTTP 500)+ 显式 return 或 os.Exit(1)。别试图“修复”或“重试”。


















