recover 不能重新抛出 panic,只能捕获并终止当前 goroutine 的崩溃;若需“重抛”,须在 defer 中显式调用 panic(r),且仅限同 goroutine 内生效,无法跨 goroutine 传递或链式传播。

recover 不能“重新抛出” panic,它只负责捕获并终止当前 goroutine 的崩溃流程;想模拟“重抛”,必须手动调用 panic,且需注意调用时机和作用域。
recover 只能捕获,不能转发或重抛
recover 的设计目标是让程序从 panic 中“软着陆”,而不是做异常链式传递。它返回的是 panic 的值(如 string 或 error),本身不带任何“继续传播”的语义。
常见误解是写成这样:
defer func() {
if r := recover(); r != nil {
// ❌ 错误:这里 r 是 interface{},不是 panic 指令
recover() // 这行毫无意义,且永远返回 nil
}
}()
实际要“重抛”,只能显式再调一次 panic:
-
panic(r)可以,但仅限在 defer 函数内、recover()之后立即调用 - 若在 defer 外调用
panic,会触发新一轮 panic,与原 panic 无关 - 多次
panic不会叠加堆栈,后一次会覆盖前一次的 panic 状态
defer 中嵌套 panic 的执行顺序容易混淆
多个 defer 按 FILO(后进先出)执行,而每个 defer 内部若再次 panic,会中断当前 defer 链,直接进入下一层 recover 判断(如果存在)。
例如:
func f() {
defer func() {
if r := recover(); r != nil {
fmt.Println("outer recovered:", r)
panic("re-raised from outer")
}
}()
defer func() {
panic("first panic")
}()
}
执行时:
- 先触发 inner
panic("first panic") - 执行 outer defer,
recover()捕获到"first panic" - 紧接着
panic("re-raised from outer")被触发,且不再有 recover 可用 → 程序崩溃
注意:外层 panic 不会保留内层 panic 的堆栈信息,Go 不支持异常嵌套链。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
跨 goroutine 的 panic 无法被其他 goroutine 的 recover 捕获
这是最常踩的坑:recover 只对**同 goroutine 内**由 panic 触发的崩溃有效。
下面这段代码中,main 的 defer 完全不会执行:
func main() {
defer fmt.Println("never printed")
go func() {
defer func() {
if r := recover(); r != nil {
fmt.Println("caught in goroutine:", r)
}
}()
panic("in goroutine")
}()
time.Sleep(10 * time.Millisecond)
}
关键点:
- goroutine A 中的
panic→ 只触发 A 自己的 defer → 只能在 A 的 defer 中recover - main goroutine 的 defer 和
recover对其他 goroutine 的 panic 完全无感 - 想统一兜底?只能靠
runtime.SetPanicHandler(Go 1.22+),但它不返回 panic 值,仅用于日志/监控
recover 后程序不会回到 panic 发生点继续执行
这是根本性限制:recover 不是“断点续传”,它只是让函数跳出 panic 流程,然后从 defer 结束处往下走。
比如:
func risky() {
defer func() {
if r := recover(); r != nil {
fmt.Println("recovered")
}
}()
fmt.Println("before panic")
panic("boom")
fmt.Println("after panic") // ❌ 永远不会执行
}
输出是:
before panic recovered
说明:
- panic 一发生,
fmt.Println("after panic")就被跳过 - recover 成功后,控制权交还给函数末尾,不是 panic 那一行
- 别指望用 recover 实现“重试逻辑”——得靠业务层显式重入或状态回滚
真正需要“重抛”的场景极少,多数时候该用 error 显式返回;只有当底层库或不可控代码触发 panic,又必须向上透传错误语义时,才考虑 panic(r)。务必记住:recover 不是 try-catch,它是一次性的、单向的、goroutine 局部的“刹车”,不是“重启键”。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










