Go中recover必须在defer注册的函数体内直接调用且panic前注册,否则无效;多个defer中仅最后一个recover生效;goroutine间panic/recover隔离,需各自处理。

defer注册顺序错在panic之后
最常见的情况是把defer写在panic()调用之后。Go要求defer语句必须在panic触发前执行,否则它根本不会被加入当前goroutine的defer链——自然也就捕获不到。
错误写法:panic("boom"); defer func() { recover() }(),此时recover()永远不会运行。
正确做法是确保defer出现在panic之前,哪怕只差一行:
func f() {
defer func() {
if r := recover(); r != nil {
log.Println("caught:", r)
}
}()
panic("boom") // 这行必须在 defer 之后
}- 函数内多个
defer按“后进先出”执行,但它们都必须在panic前注册完成 - 如果函数有分支逻辑(如if/else),要确认所有可能路径上
defer都已注册 - 不要依赖
init()或包级defer来捕获main中panic——它们不在同一函数作用域
recover没在defer函数体内直接调用
recover()只有在defer注册的函数体内部、且是**直接调用**才有效。一旦包在另一层函数里(哪怕是立即执行的闭包),就失效了。
立即学习“go语言免费学习笔记(深入)”;
错误示例:defer func() { go func() { recover() }() }() 或 defer func() { helper() }(); func helper() { recover() } —— 这两种都会返回nil。
正确写法必须是:defer func() { r := recover(); /* 处理r */ }(),recover()不能有任何中间调用层。
- 检查是否误用了
go关键字启动新goroutine来调recover,这会脱离原goroutine上下文 - 避免在
defer里调用其他函数再间接调recover,哪怕那个函数只有一行 - IDE里可以加断点验证:panic发生后,是否真的进入了defer函数体?
recover()那行有没有执行?
多个defer里混用recover,只有最后一个生效
一个函数里注册多个defer func() { recover() }(),看起来像“多加几道保险”,其实只会让前面的都返回nil——因为panic只发生一次,而recover()只能捕获正在传播中的那个,且捕获后panic状态就被清除了。
典型现象:日志里看到多次recover() == nil,但程序仍崩溃。
- 删掉重复的
recover(),只留一个明确位置的(通常放在最外层defer) - 如果需要不同场景做不同清理,把逻辑写进同一个
recover()分支里,而不是拆成多个defer - 注意命名返回值场景:
defer里修改返回值是可行的,但recover()之后继续执行的代码,看到的是panic前的状态快照,不是“重试”
goroutine隔离导致主流程recover完全无效
主线程写了defer+recover,但panic发生在go func() { ... }()里——这种recover永远不生效。每个goroutine的panic/recover是完全隔离的。
HTTP handler里起goroutine处理请求,里面panic了,handler本身毫无感知,日志也静默丢失。
- 必须在每个可能panic的goroutine内部单独加
defer func() { recover() }() - 封装
safeGo时,不能只在外层包一层go,得确保传入函数本身也带recover,或者用wrapper统一注入 - 不要指望“全局recover”兜底——Go没有这种机制,
init()里的defer只对init阶段有效
真正容易被忽略的点是:recover成功后,函数不会回到panic那行重试,局部变量、指针、切片长度等状态大概率已损坏。别在recover分支里继续调用DB写入或HTTP请求这类依赖完整状态的操作,只做记录、降级、显式return或os.Exit(1)。


















