recover必须在defer中调用才有效,且仅在同goroutine中panic发生前注册的defer里生效;跨goroutine、普通调用或panic后新建goroutine调用均返回nil。

recover必须在defer中调用才有效
Go里recover不是全局“捕获panic”的钩子,它只在当前goroutine的defer函数中调用时才可能生效。如果写成普通函数调用、或在panic发生后才起一个新的goroutine去recover,结果一定是nil。
常见错误写法:
func badHandler() {
recover() // 这里永远返回 nil,panic还没发生,且不在defer里
doSomething()
}
正确姿势是:在可能panic的函数入口立即注册defer,并在其中调用recover:
- 必须在同一个goroutine中,且
defer要早于panic发生 - 不能跨函数边界“延迟recover”——比如把recover逻辑抽到另一个函数里再defer调用,容易漏掉作用域
- recover后原栈已终止,无法继续执行panic点之后的代码,只能做清理或重试决策
策略函数中recover的典型封装模式
策略函数(比如func() error类型)常用于插件化流程控制,但内部可能因校验失败、空指针、第三方库异常等panic。这时候需要统一兜底,而不是让整个流程崩溃。
立即学习“go语言免费学习笔记(深入)”;
推荐封装一个带recover的执行器:
func RunWithRecover(strategy func()) (err error) {
defer func() {
if r := recover(); r != nil {
switch x := r.(type) {
case string:
err = fmt.Errorf("panic: %s", x)
case error:
err = fmt.Errorf("panic: %w", x)
default:
err = fmt.Errorf("panic: %v", x)
}
}
}()
strategy()
return
}
使用时直接包一层:
err := RunWithRecover(func() {
riskyStrategy()
})
- 不要在策略函数内部自己写defer+recover——会污染业务逻辑,且难以统一错误处理语义
- 如果策略函数有返回值(如
func() (int, error)),recover封装需适配对应签名,不能只处理error型 - 注意:recover不处理由
os.Exit、信号终止、或runtime.Goexit引发的退出
recover后如何安全地继续执行流程
recover本身只是停止panic传播,并不“恢复”程序状态。变量可能处于不一致态,锁可能未释放,channel可能卡住。所以recover后不能盲目继续原逻辑,而应明确进入降级路径。
- 优先记录panic详情(含堆栈),至少用
debug.PrintStack()或runtime/debug.Stack() - 避免在recover块里调用可能再次panic的函数(比如未判空的map操作、未初始化的接口断言)
- 若策略函数本该返回结果,recover后应提供合理默认值或明确错误,而非返回零值误导上层
- 并发场景下,recover不能替代同步机制——比如在
sync.Mutex.Lock()后panic,recover不会自动Unlock,必须显式处理
为什么log.Fatal或os.Exit会让recover失效
很多策略函数末尾习惯写log.Fatal("xxx"),但这其实会调用os.Exit(1),直接终止进程,不触发任何defer,自然也绕过recover。
真正需要recover的panic,必须来自语言级异常(如索引越界、nil指针解引用、channel关闭后发送等),而不是主动退出。
-
log.Fatal→ 调用os.Exit→ 无defer执行 → recover不可见 -
panic("xxx")→ 触发panic流程 → defer按序执行 → recover可捕获 - 若策略函数内混用二者,recover只能兜住panic,对Fatal完全无感
线上策略系统里,建议统一禁用log.Fatal和os.Exit,全部转为返回error并由外层决定是否终止。


















