recover必须在defer匿名函数内调用才能捕获panic,否则返回nil;跨goroutine panic无法被主goroutine recover捕获;recover后应立即记录并退出,不可继续业务逻辑或访问可能损坏的状态。

recover必须在defer匿名函数里调用,否则永远返回nil
这是最常踩的坑:把recover()写在普通函数体、if分支或提前return之后,它根本不会执行,更别说捕获了。Go 运行时只在 panic 正在传播、且当前 goroutine 尚未退出时,才允许 recover() 拦截——这个窗口期只存在于已注册的 defer 函数体内。
正确写法只有这一种模式:
defer func() {
if r := recover(); r != nil {
log.Printf("panic caught: %v", r)
}
}()
-
defer recover()是错的:它会在注册时立刻执行recover(),此时还没 panic,必然返回nil - 不能包在另一层函数里,比如
go func() { recover() }()或func() { recover() }()—— 这些都不是 defer 链上正在执行的函数 - 必须确保 defer 语句出现在 panic 发生前;写成
panic("x"); defer func() { recover() }()就完全失效
跨 goroutine 的 panic 无法被主 goroutine 的 recover 捕获
主线程里加了 defer+recover,对 go func() { panic("index out of range") }() 完全没反应。子 goroutine 崩溃后静默退出,主 goroutine 不知情,进程照样崩溃退出。
每个 goroutine 的 panic 是隔离的,recover() 没有跨协程传播能力。想防止单个 worker 崩掉整个服务,就得在每个 worker 内部自己加防护:
go func() {
defer func() {
if r := recover(); r != nil {
log.Printf("worker panic: %v", r)
}
}()
doWork()
}()
- HTTP handler 中起 goroutine 处理耗时任务 → handler 里的 recover 不会触发,日志无声丢失
- 封装
safeGo(fn)工具函数时,必须确保传入的fn内部也带defer+recover,否则只是把崩溃从显式变成隐蔽 - 别指望“全局 recover 中间件”——它只对当前 goroutine 有效
recover 后不能继续业务逻辑,状态大概率已损坏
recover() 成功后,程序不会回到 panic 那一行重试,而是从 defer 函数返回后继续执行后续语句。但此时局部状态大概率已破坏:数组越界后 slice 可能部分初始化、map 被并发写入后内部结构已损坏、指针可能已解引用失败。
常见错误是 recover 后还继续调用 close(ch)、向已 panic 的 map 写入、或访问刚解引用的空指针字段——这会引发二次 panic,覆盖原始错误信息。
- 推荐模式:
if r := recover(); r != nil { log.Printf("panic: %v", r); return }—— 记录完就退出当前函数 - 若需清理资源(如关闭文件、释放锁),应放在独立的 defer 链里,而不是 recover 分支中
- 不要在 recover 分支里发 HTTP 请求、写 DB、或调用任何依赖当前函数局部状态的业务代码
打印完整堆栈必须用 runtime/debug.Stack(),且要立即调用
recover() 只返回 panic 的值(比如 "division by zero" 或 errors.New("db fail")),不带位置信息。要看到哪一行 panic 的,必须配合 runtime/debug.Stack() —— 它返回当前 goroutine 的完整调用栈字节切片。
注意:debug.Stack() 必须在 recover() 后**立即**调用,否则后续 defer 执行可能导致栈变化;返回值是 []byte,需用 string() 转换才能打印:
defer func() {
if r := recover(); r != nil {
log.Printf("panic: %v\n%s", r, string(debug.Stack()))
}
}()
- 开发期可全量保留栈,生产环境建议截取前 4KB,避免日志爆炸
- 不要用
fmt.PrintStack():它直接输出到os.Stderr,不可控、难采集 - 若需重抛 panic(如中间件统一记录后交由上层处理),必须先获取
debug.Stack(),再panic(r)—— 否则原始栈信息丢失
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











