recover只能在同goroutine的defer函数中捕获panic,跨goroutine调用无效;必须在每个可能panic的goroutine内部配对使用defer+recover,并显式清理资源,不可替代正常错误处理。

recover在goroutine里根本捕不到panic
直接上结论:recover 只能在 defer 函数中、且该函数必须与引发 panic 的代码在同一个 goroutine 里执行,才有效。如果在子 goroutine 里 panic,主 goroutine 调用 recover 是完全没用的——这是最常踩的坑。
典型错误写法:
go func() {
panic("boom")
}()
// 这里 recover 永远返回 nil
if r := recover(); r != nil {
log.Println("caught:", r)
}原因很简单:每个 goroutine 有独立的调用栈,recover 只能“恢复”当前 goroutine 的 panic,不能跨栈操作。
正确做法:在goroutine内部配对使用defer+recover
想让某个工作协程不因 panic 而静默退出,必须把 recover 放进它自己的执行流里,且紧挨着可能出错的逻辑。
立即学习“go语言免费学习笔记(深入)”;
实操建议:
- 所有长期运行或处理不可信输入的 goroutine,都应包裹一层
defer func(){ if r := recover(); r != nil { /* 记录日志或上报 */ } }() - 不要只打印
r,至少带上 goroutine 标识(比如传入的id或用runtime.GoID()(需 Go 1.22+)) - recover 后别继续原逻辑——panic 已破坏当前栈状态,应安全退出或重试(视场景而定)
示例:
go func(workerID int) {
defer func() {
if r := recover(); r != nil {
log.Printf("worker %d panicked: %v", workerID, r)
// 可选:发 metric、触发告警、写入错误队列
}
}()
doWork() // 可能 panic 的业务逻辑
}(101)recover后如何避免goroutine泄露
recover 能止住 panic,但不等于这个 goroutine 就“完成任务”了。如果它本该持续运行(比如监听 channel),recover 后若直接 return,就永久退出;若没加控制,又可能无限重启——两种都算泄露(语义层面)。
关键判断点:
- 如果是“一次性任务”,recover 后 clean up 并 return 即可
- 如果是“长稳协程”(如
for range ch),recover 后应继续循环,但要确保 panic 前的状态没污染后续迭代(比如 channel 已 close、map 已被并发写坏) - 更稳妥的做法是:recover 后打日志 +
time.Sleep避免抖动 + 重新初始化关键状态(如重开 channel、重建 map)
注意:recover 不会自动重置已损坏的变量或关闭的 channel,这部分必须手动处理。
比recover更值得优先考虑的替代方案
靠 recover 补救,本质是防御性编程的下策。真正健壮的服务应该尽量让 panic 不发生:
- 用
errors.Is和errors.As显式处理预期错误,而不是靠 panic 分流 - 对第三方库调用(尤其是反射、
unsafe、模板渲染)加输入校验,提前拦截非法参数 - 用
sync.Pool管理临时对象时,避免在Get后直接断言类型——先判空再 assert - 单元测试覆盖边界 case(nil pointer、空 slice、超大数字),比线上 recover 更早暴露问题
recover 不是 try-catch,它不解决根本问题,只防止进程级崩溃。真正难的是厘清哪些 panic 属于“不该发生”,哪些属于“不可控外部输入”——这个分界线,比怎么写 defer recover 重要得多。


















