Go中panic和recover不是异常机制,仅用于不可恢复错误或函数级边界控制;recover必须在defer中调用才有效,且仅捕获同goroutine中未被拦截的panic,不能跨协程生效。

Go 里没有传统意义上的“异常捕获”,panic 和 recover 不是 try/catch,强行当异常用会出大问题——它们只该用于真正不可恢复的程序错误或函数级边界控制。
为什么不能在任意位置 defer recover()
recover 只有在 defer 函数中调用才有效,且仅对当前 goroutine 中由 panic 触发的栈展开过程起作用。如果在非 defer 函数里调用,或者在 panic 已经传播出当前函数之后再调用,recover 返回 nil,毫无效果。
- 常见错误:把
recover()放在普通 if 分支里,期待它“拦截” panic —— 它根本不会执行到那行 - 更隐蔽的坑:在 goroutine 里启动新协程并 panic,主 goroutine 的
recover完全无感 - 正确姿势:必须写成
defer func() { if r := recover(); r != nil { /* 处理 */ } }()
recover 能捕获哪些 panic?
recover 只能捕获当前 goroutine 中、尚未被其他 recover 拦截过的 panic。它不区分 panic 类型(string、error、自定义 struct 都行),但返回值是 interface{},需要类型断言才能安全使用。
- 直接
panic("oops")→recover()返回"oops"(string类型) -
panic(errors.New("io failed"))→recover()返回该*errors.errorString,可断言为error - 若 panic 值是
nil,recover()也返回nil,无法区分“没 panic”和“panic(nil)” - 不要依赖 panic 值做业务逻辑分支——它本就不是设计来传业务状态的
函数级隔离:用 recover 包裹单个函数调用
想让某个函数崩溃不影响整体流程,最稳妥的方式是在调用它的地方加一层 defer + recover,把它变成“可失败的子任务”。这比全局 panic handler 更可控,也避免污染调用链。
立即学习“go语言免费学习笔记(深入)”;
func runWithRecover(fn func()) (err error) {
defer func() {
if r := recover(); r != nil {
switch x := r.(type) {
case string:
err = fmt.Errorf("panic: %s", x)
case error:
err = fmt.Errorf("panic: %w", x)
default:
err = fmt.Errorf("panic: %v", x)
}
}
}()
fn()
return
}
<p>// 使用示例
if err := runWithRecover(func() {
http.ListenAndServe(":8080", nil) // 可能 panic(端口被占)
}); err != nil {
log.Printf("server crashed: %v", err)
}
- 注意:这个包装函数本身不能 panic,否则外层又得包一层——容易套娃
- 别在
runWithRecover里打印日志或重试,那是调用方该决定的事 - 如果 fn 内部已含
recover,外层 wrapper 就捕不到——这是设计意图,不是 bug
最容易被忽略的限制:recover 不能跨 goroutine 生效
这是绝大多数 Go 新手踩坑的根源。你写了 defer recover(),但 panic 发生在另一个 goroutine 里,结果什么都没拦住,程序照样退出。
- 错误写法:
go func() { panic("in goroutine") }()→ 主 goroutine 的 recover 看不见 - 正确思路:每个可能 panic 的 goroutine 自己负责
defer recover() - 更推荐做法:根本不要让 goroutine panic。用
return error+select+ channel 通信替代 - 如果必须用 panic(比如第三方库内部 panic),只能在 goroutine 启动时就加上 recover,例如:
go func() { defer recover(); doWork() }()
真正难的不是怎么写 recover,而是判断“这里到底该不该 panic”。多数时候,返回 error 更清晰;只有当函数完全无法继续执行、且调用方不可能合理处理时,才考虑 panic——然后立刻用 defer recover 划清责任边界。


















