recover仅在defer函数中有效且必须立即处理panic,不可滥用;其后需立即调用debug.Stack()获取堆栈并显式返回,各goroutine须独立防护。

recover 不是 Go 的错误处理主力,它只在极少数场景下有用:比如防止某个 goroutine 崩溃拖垮整个服务,或在测试中模拟崩溃路径。滥用它会让程序状态不可控、bug 难以复现、日志失真。
recover 必须写在 defer func() { }() 里
这是硬性前提,不是建议。任何其他写法都无效:
-
defer recover()—— 错。这会在注册时就执行recover(),此时还没 panic,必然返回nil -
if r := recover(); r != nil { }写在函数体任意位置 —— 错。永远返回nil,因为不满足“panic 正在传播 + 当前 defer 正在执行”的条件 - 在子 goroutine 外层函数里写 defer —— 错。主线程的
recover()对go func() { panic("x") }()完全无感
正确结构只有一种:
func risky() {
defer func() {
if r := recover(); r != nil {
// 处理
}
}()
// 可能 panic 的代码
}recover 后不能继续用原状态
recover 只是让函数“不崩溃退出”,不代表内存、channel、map、slice 等还安全。越界访问 slice 后 recover,那个 slice 可能已处于未定义行为;并发写 map 后 recover,map 内部结构大概率已损坏。
立即学习“go语言免费学习笔记(深入)”;
- 不要在
recover块里继续向同一个 channel 发送、close已 close 的 channel、或读写刚 panic 的 map - 资源清理(如
os.File.Close()、sync.Mutex.Unlock())应放在独立的defer中,而不是依赖recover后的逻辑 - 推荐模式:
if r := recover(); r != nil { log.Printf("panic: %v", r); return }—— 记录完立刻退出函数
打印堆栈必须用 debug.Stack()
recover() 返回的是 interface{},只含 panic 值(比如 "index out of range" 或 errors.New("db timeout")),不含文件名、行号、调用链。要定位问题,必须配合 runtime/debug.Stack()。
-
debug.Stack()返回[]byte,需转成string()才能打印或记录 - 必须在
recover()后立即调用,否则后续defer执行可能改变栈帧,导致堆栈信息不准 - 生产环境建议截断长度(如
string(debug.Stack()[:min(len(debug.Stack()), 4096)])),避免日志刷爆磁盘
典型写法:
defer func() {
if r := recover(); r != nil {
log.Printf("panic: %v\n%s", r, string(debug.Stack()))
}
}()每个 goroutine 都得自己加 recover
Go 的 panic 是 goroutine 局部的,没有“全局捕获”机制。HTTP handler、worker 协程、定时任务 goroutine,都必须各自防护。
- HTTP 中间件里写
defer func(){...}(),只能保当前请求,不影响其他连接 - 别指望一个
main()里的 defer 能兜住所有子 goroutine - 封装工具函数更可靠,比如:
goSafe(func() { /* 业务 */ }),内部自动注入defer func(){ recover() }() - 使用
errgroup.Group时,它的Go()方法已内置 recover,可直接用
最易被忽略的一点:recover 后若没显式 return 或 panic 新错误,后续代码仍会执行——而这时变量可能已处于非法状态,二次 panic 几乎不可避免。


















