recover必须在defer中调用才有效,且需位于panic发生的同一goroutine中;它仅止血不恢复栈,返回interface{}需类型断言,无法跨goroutine捕获panic。

recover 必须在 defer 中调用才有效
直接在普通函数体里写 recover() 没用,它永远返回 nil。Go 的 panic/recover 机制设计上就要求 recover() 出现在 defer 函数里,且该 defer 必须在 panic 发生的同一 goroutine 中、尚未返回的函数内注册。
常见错误是把 recover() 放在顶层逻辑或异步 goroutine 里:
func badExample() {
go func() {
recover() // 永远 nil:不在 panic 的同 goroutine 里
}()
}
- 正确做法:每个可能 panic 的函数内部,用
defer包一层匿名函数,并在其中调用recover() - 如果想统一处理,可封装为工具函数,但本质仍是往当前函数塞
defer - 注意:
defer注册顺序是后进先出,多个defer时,recover()要放在最靠近 panic 可能发生位置的那个defer里
recover 只能捕获当前 goroutine 的 panic
这是最常被忽略的边界条件。goroutine 是独立的执行单元,panic 不会跨 goroutine 传播,recover() 也无权“越界”拦截。
典型误用场景:
func main() {
defer func() {
if r := recover(); r != nil {
log.Println("caught:", r)
}
}()
go func() {
panic("from goroutine") // 这个 panic 不会被上面的 recover 捕获
}()
time.Sleep(time.Millisecond)
}
- 每个需要容错的 goroutine 都得自己配
defer + recover - 主 goroutine 的
recover对子 goroutine 完全无效 - 若用
sync.WaitGroup等待子 goroutine,也不能靠主 goroutine 的 recover 补救——子 goroutine panic 后直接终止,WG 不会卡住,但错误被静默丢弃
recover 返回值类型是 interface{},需类型断言判断 panic 内容
recover() 返回的是 interface{},不是 error。直接打印或比较容易出错,尤其当 panic 传入的是字符串、整数或自定义结构体时。
示例:
func risky() {
defer func() {
if r := recover(); r != nil {
switch x := r.(type) {
case string:
log.Printf("panic as string: %s", x)
case error:
log.Printf("panic as error: %v", x)
default:
log.Printf("panic as unknown type %T: %v", x, x)
}
}
}()
panic("something went wrong") // 触发 string 分支
}
- 不要假设 panic 一定是
error类型;标准库和多数代码用panic(err),但业务代码常用panic("msg") - 不加
switch或if v, ok := r.(error)判断就直接调v.Error()会导致再次 panic - 日志中建议同时输出
%T和%v,方便排查 panic 来源类型
recover 无法恢复栈,函数仍会按 defer 顺序退出
recover() 的作用只是“止血”,让程序不崩溃,但它不会回滚或重试已执行的语句,也不会让函数继续往下走。调用 recover() 后,当前函数会立即开始执行所有已注册的 defer,然后返回(返回值按函数签名默认零值,除非显式赋值)。
这意味着:
- 不能靠
recover()实现“重试逻辑”——它不提供控制流跳转能力 - 如果函数有返回值,且希望在 recover 后返回特定值,必须在 defer 里显式赋值给命名返回值,例如:
func f() (err error) { defer func() { if r := recover(); r != nil { err = fmt.Errorf("recovered: %v", r) } }() } - defer 中修改局部变量不影响外层逻辑,只有命名返回值或指针/引用传递才能带出状态
真正难处理的,是 panic 发生在中间状态(比如文件已打开但未写完、DB 事务已 begin 但未 commit),recover 能阻止崩溃,但没法自动清理这些半截资源——这部分必须靠手动 defer 关闭或 rollback,和 recover 本身无关。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











