recover必须在defer中调用才有效,仅在当前goroutine的defer函数内调用才能捕获panic;普通函数调用无效,且无法跨goroutine捕获。

recover必须在defer中调用才有效
Go里recover不是全局“catch”机制,它只在当前goroutine的defer函数中调用时才可能捕获到panic。如果写成普通函数调用(比如在panic后立刻调用recover()),返回值永远是nil,因为此时已经不在延迟执行上下文中。
常见错误写法:
func badExample() {
panic("boom")
recover() // 这行根本不会执行,且就算执行也无效
}
正确姿势是把recover包在defer里:
func goodExample() {
defer func() {
if r := recover(); r != nil {
fmt.Println("caught:", r)
}
}()
panic("boom") // 这里触发,之后defer执行,recover生效
}
跨函数捕获的本质是跨栈帧,不是跨函数名
所谓“跨函数”,其实是panic沿着调用栈向上冒泡,直到遇到包含recover的defer。这个defer可以定义在任何上层函数里,不一定要和panic在同一个函数中。
立即学习“go语言免费学习笔记(深入)”;
例如:
-
main里defer一个匿名函数,里面调用recover - 然后调用
foo(),foo()再调用bar(),bar()里panic - panic会一路返回到
main的defer,被成功捕获
关键点:recover生效依赖的是「调用栈深度 + defer注册时机」,而不是函数名或包名。只要defer在panic发生前注册,并且没被提前return/exit绕过,就能捕获。
recover无法捕获其他goroutine的panic
每个goroutine有独立的panic/recover作用域。A goroutine里panic,B goroutine里的defer+recover完全无感知。
典型误用场景:
- 在
go f()启动的协程里panic - 主goroutine试图用
defer+recover去兜底——这做不到
解决方法只有两种:
- 在引发panic的goroutine内部做
defer+recover(最常用) - 用
sync.WaitGroup配合chan interface{}把panic信息传出来(需手动包装,不推荐日常用)
注意:http.Server等标准库组件已内置recover逻辑,所以HTTP handler里panic通常不会导致进程退出,但这属于库实现细节,不是语言特性。
recover后程序继续执行,但栈已展开完毕
recover成功后,当前goroutine不会终止,而是从panic发生的下一行(实际是调用栈顶层的defer之后)继续执行。但要注意:所有在panic路径上的中间函数都已经return了,它们的局部变量、defer语句(除了正在执行的那个)都已失效。
容易踩的坑:
- 在recover后的代码里访问之前函数的局部变量——这些变量可能已被回收或不可靠
- 以为recover能“回滚”状态,其实不能;数据库事务、文件写入等需显式回退
- recover后继续调用可能再次panic的函数,又没包新的defer——容易漏捕获
真实项目中,recover后建议只做日志记录、清理资源、返回错误,避免复杂逻辑分支。


















