recover只能捕获当前goroutine内、panic发生前已注册的defer函数中的异常,跨goroutine无效,且必须在panic触发前注册defer、在defer体内直接调用recover,否则返回nil。

recover必须在panic发生的同一goroutine中注册
跨goroutine的panic完全无法被父goroutine里的recover捕获。比如在go func() { panic("boom") }()里触发panic,哪怕main里写了defer func() { recover() }(),也拿不到任何值——recover()返回nil,程序照样退出。
真正有效的做法是:每个可能panic的goroutine内部,自己注册defer func() { recover() }()。HTTP handler、定时任务、worker goroutine都该这么处理。
- 子goroutine的panic不会传播到父goroutine,也不会触发父goroutine的defer
- 主goroutine里defer的
recover()只对它自己函数体内发生的panic有效 - 框架如Gin/echo的recover中间件,本质就是在每个handler goroutine入口处加了defer+recover
recover只能捕获尚未退出当前函数的panic
recover()不是“全局监听器”,它只在panic正在向上传播、但还没离开当前函数栈帧时起作用。一旦panic导致函数返回,recover()就永远失效。
常见错误是把defer写在panic之后:
func bad() {
panic("now")
defer func() { recover() }() // 永远不执行
}
正确顺序必须是先注册defer,再触发panic:
func good() {
defer func() {
if r := recover(); r != nil {
log.Println("caught:", r)
}
}()
panic("now") // 此时recover才可能生效
}
- defer语句必须在panic发生前执行(即函数还没崩溃)
- 如果panic发生在调用链更深层(比如A→B→C→panic),只要A/B/C中任意一层有提前注册的defer+recover,就能捕获
- 但若所有层都没注册,panic会一路冒泡到goroutine起点,然后终止
recover对runtime.fatal类错误完全无效
不是所有以panic:开头的输出都能被recover()捕获。关键看底层触发机制:
在 Go 中使用 google/wire 实现编译时依赖注入——wire.NewSet、wire.Build、wire.Bind(接口→实现)、wire.Struct、wire.Value、wire.Interface
- 能捕获:
panic("msg")、数组越界、nil指针解引用(部分)、map写入nil等由runtime.panic()发起的恐慌 - 不能捕获:
fatal error: stack overflow、throw: out of memory、invalid memory address or nil pointer dereference(某些底层场景)、signal SIGKILL
判断依据很简单:看错误是否伴随完整goroutine stack trace。有trace的一般可recover;直接fatal error:开头或进程静默退出的,recover插不上手。
这类错误意味着运行时已无法维持基本状态,Go选择立即终止,不给recover介入机会。
多个defer中recover的位置决定能否捕获成功
多个defer按后进先出(LIFO)顺序执行。recover()必须放在**最靠近panic触发点的那个defer里**,否则会被前面的defer“提前消耗”掉返回值。
比如:
func f() {
defer func() { fmt.Println("first defer") }()
defer func() {
if r := recover(); r != nil {
fmt.Println("got it:", r) // ✅ 这里能拿到panic值
}
}()
panic("hello")
}
如果把recover()放在第一个defer里,第二个defer执行时recover()就返回nil了。
- recover只能被调用一次,且仅对当前正在传播的panic有效
- 同一个panic被多次recover调用,只有第一次返回非nil值
- 别在多个defer里都写recover——浪费,还可能掩盖问题定位
实际编码中,一个函数里通常只设一处recover,放在函数开头的defer里最稳妥,覆盖所有panic路径。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










