recover必须写在defer函数里,否则永远返回nil;它仅在panic传播且defer执行时有效,需在panic前注册defer,且每个goroutine须独立防护。

recover 必须写在 defer 函数里,否则永远返回 nil
这是最常踩的坑:把 recover() 直接写在普通代码流中,比如放在 if 分支里、或者函数开头就调,它根本不会起作用。因为 recover() 只有在 panic 正在传播、且当前 defer 正被执行时,才能截获 panic 值;其他任何时机调用都返回 nil。
正确写法只有一种模式:
defer func() { if r := recover(); r != nil { /* 处理 */ } }()- 这个
defer语句必须出现在 panic 触发之前(哪怕只差一行) - 不能用命名返回变量覆盖值——
recover()后需显式return,否则可能返回未初始化的零值
每个 goroutine 都要独立加 defer+recover,主 goroutine 捕不到子协程 panic
Go 的 panic 是 goroutine 局部的。你在 go func() { panic("x") }() 里崩了,外面无论套多少层 defer 都看不见。这不是 bug,是并发模型的设计前提。
典型翻车场景:
- HTTP handler 中启 goroutine 做异步任务,里面 panic → handler 完全无感知,协程静默退出
- 后台 worker 没包
recover→ 连接卡住、定时任务漏跑、数据库事务没回滚 - 用
errgroup.Group或封装goSafe(func(){})才能统一防护
recover 后不能继续使用已损坏的状态
recover() 让程序“继续往下跑”,但不代表状态安全。越界访问 slice 后 recover,那个 slice 可能已处于未定义行为边缘;向已 close 的 channel 再写入,可能二次 panic。
安全做法是:
- recover 后应立即记录日志、清理资源、返回,而不是试图“修复”并继续逻辑
- 避免在 recover 块里调用
close(ch)、向 map 写入、或读取可能已被破坏的 struct 字段 - 推荐模式:
if r := recover(); r != nil { log.Printf("panic: %v", r); return }
panic 值是 interface{},直接 fmt.Println(recover()) 会打印 <nil></nil>
recover() 返回的是 interface{},可能是 string、error、自定义 struct 等。不判断 r != nil 就直接打日志,结果永远是 <nil></nil>;不显式转字符串,日志里可能为空或乱码。
安全打印方式:
- 通用:用
fmt.Sprint(r)或fmt.Sprintf("%v", r) - 结构化处理:先做类型断言,
if err, ok := r.(error); ok { /* 处理 error */ } - HTTP 中建议搭配
debug.Stack()记录完整堆栈,服务端日志保留上下文,响应体则脱敏
真正难的不是写出那三行 defer func(){recover()},而是在 panic 发生后,快速判断哪些变量还可信、哪些资源已不可用、是否该终止当前请求而非强行续跑。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











