recover必须在defer函数中调用才有效,直接在普通函数体调用永远返回nil;它仅能捕获同goroutine中已注册的defer所处上下文内的panic,无法跨goroutine生效,且panic后栈已展开,recover仅用于终止panic并执行清理。
直接在普通函数体里写 recover() 永远返回 nil,因为它只能捕获当前 goroutine 中、且由同层 defer 延迟执行时发生的 panic。Go 的 panic/recover 不是传统 try-catch,而是一次性、仅限于 defer 上下文的“现场抢救”。
recover() 只在 defer 函数中被调用时才可能非 nilrecover() 完全无感知常见误写是把 recov 写在普通逻辑块里,或者 defer 里没调用它,又或者 defer 写在 panic 之后——这些都导致恢复失败。下面这个例子看似合理,实则无效:
func badExample() {
panic("boom")
defer func() {
if r := recover(); r != nil {
fmt.Println("never reached")
}
}()
}正确写法必须保证 defer 在 panic 前注册,且 recover 在 defer 函数体内调用:
func goodExample() {
defer func() {
if r := recover(); r != nil {
fmt.Printf("recovered: %v\n", r) // 输出: recovered: boom
}
}()
panic("boom")
}每个 goroutine 有独立的 panic/recover 作用域。主 goroutine 中的 defer + recover 对子 goroutine 的 panic 完全无效。这是最容易忽略的限制。
sync.WaitGroup 或 errgroup.Group 时,尤其要注意 panic 是否被各自 goroutine 自行 recover例如以下代码中,main 的 recover 捕不到 goroutine 里的 panic:
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println("not triggered")
}
}()
go func() {
panic("in goroutine") // 这个 panic 不会被上面的 recover 捕获
}()
time.Sleep(10 * time.Millisecond)
}recover 成功后,panic 被终止,控制权回到 defer 所在函数,后续代码照常运行——但注意:panic 发生点之后的所有 deferred 函数(尚未执行的)仍会按 LIFO 顺序执行,且 recover 只能调用一次(再次调用返回 nil)。
真正需要的是明确知道 panic 可能发生在哪里,并在最靠近风险点的位置做隔离和恢复,而不是靠一层大 defer 包住整个 main 函数。