recover只能捕获同goroutine的panic,跨goroutine无效;必须在panic前用defer注册匿名函数并内调recover;返回值需类型断言或fmt.Sprint安全处理,且无法捕获runtime级崩溃。

recover 只能捕获同 goroutine 的 panic
跨 goroutine 的 panic 永远无法被 recover 捕获,这是 Go 运行时的硬性约束,不是 bug,也不是配置问题。主 goroutine 里写再多 defer + recover,也拦不住子 goroutine 里的 panic。
常见翻车场景:
- 用
go fn()启动一个函数,里面panic("db timeout") - 主函数写了
defer func() { recover() }(),但什么也没打印,程序照样崩溃退出
解决办法只有一条:每个可能 panic 的 goroutine 必须自己注册 defer + recover。可以封装成工具函数:
func safeGo(fn func()) {
go func() {
defer func() {
if r := recover(); r != nil {
log.Printf("goroutine panic: %v", r)
}
}()
fn()
}()
}
调用时写 safeGo(func() { ... }),而不是裸写 go。
recover 必须在 defer 匿名函数体内直接调用
recover() 不是普通函数,它依赖当前 goroutine 的 panic 上下文。一旦脱离 defer 的执行帧,就失效了。
以下写法全部无效:
-
defer recover()——recover在注册时就执行,返回nil -
defer func() { safeRecover() }(),其中safeRecover内部调用recover()—— 调用栈已偏移,上下文丢失 -
if r := recover(); r != nil { ... }写在普通代码块或if分支里 —— 不在 defer 中,永远不生效
唯一有效的模式是:
defer func() {
if r := recover(); r != nil {
// 处理 r
}
}()
注意:这个 defer 必须在 panic 发生前注册,顺序不能颠倒。
recover 返回 interface{},类型断言失败会再次 panic
recover() 总是返回 interface{},而 panic 参数可以是任意类型:string、error、int、自定义结构体……直接强转极易二次崩溃。
错误示范:
-
err.(error).Error()—— 如果 panic 传的是"oops"字符串,这里会 panic -
fmt.Errorf("wrap: %w", r)——%w要求r是error,否则 panic
安全做法分两步:
- 先判断是否为
error类型:if err, ok := r.(error); ok { ... } - 否则统一用
fmt.Sprint(r)转字符串,避免崩溃:fmt.Errorf("panic value: %v", r)
更健壮的写法是 switch 类型分支:
switch x := r.(type) {
case error:
err = x
case string:
err = errors.New(x)
default:
err = fmt.Errorf("panic: %v", x)
}
recover 拦不住 runtime 级崩溃
recover 能捕获你写的 panic("xxx")、空指针解引用、越界访问这些语言级 panic,但对底层运行时崩溃完全无能为力。
这些情况 recover 一律失效:
-
Cgo调用 C 代码导致段错误(SIGSEGV) - 栈溢出(stack overflow)
- 内存耗尽(OOM),触发
runtime: out of memory - 显式调用
os.Exit(1)或syscall.Exit
这意味着:不要指望 recover 能兜住所有“程序挂了”的场景。线上服务若频繁出现未捕获崩溃,优先查 Cgo、大对象分配、无限递归,而不是加更多 recover。











