recover必须在defer中调用才有效,仅对当前goroutine的panic生效;需在defer匿名函数内调用,返回interface{}须类型断言;不能回滚状态,仅退出panic并继续执行;子goroutine需各自recover。

recover必须在defer中调用才有效
Go 没有传统 try/catch,recover 只能在 defer 函数里起作用,且仅对当前 goroutine 中由 panic 触发的异常生效。如果写在普通函数体里,recover() 总是返回 nil,什么也捞不到。
常见错误是把 recover 放在 if 判断块里、或者放在 panic 后面但没包在 defer 里:
func badExample() {
panic("boom")
recover() // 这行永远不执行,且即使执行也返回 nil
}
- 必须用
defer func() { ... }()包一层 - 该匿名函数不能带参数(否则无法捕获 panic 值)
- 如果嵌套多层 defer,只有最内层能捕获到 panic,外层看到的是已恢复的状态
recover后程序继续执行,但栈已展开完毕
调用 recover() 不会“回滚”已执行的语句,也不会还原局部变量。它只是让 goroutine 从 panic 状态中退出,控制流回到 defer 所在函数的末尾,然后继续向下执行。
这意味着:
立即学习“go语言免费学习笔记(深入)”;
- defer 里的代码仍会按注册顺序倒序执行(包括其他未触发的 defer)
- panic 发生前已修改的全局变量、指针所指内存、文件句柄等状态不会自动回退
- 如果想模拟“事务回滚”,得手动保存快照或设计补偿逻辑,
recover本身不提供这个能力
典型误用:recover() 后直接 return 而不检查错误类型,导致本该终止的流程继续跑出脏数据。
recover只能捕获当前goroutine的panic
Go 的 panic 是 goroutine 局部的,主 goroutine panic 不会影响子 goroutine,反过来也一样。所以你在 main 函数里 defer recover,只对 main 里发生的 panic 有效;goroutine 内部 panic,必须在那个 goroutine 内部做 defer + recover。
常见兜底场景如 HTTP handler 或后台任务,容易漏掉 goroutine 级别的 recover:
go func() {
// 这里 panic 不会被 main 的 recover 捕获
panic("in goroutine")
}() // 必须在这里加 defer recover
- HTTP server 默认不为每个 handler 启动独立 recover,需显式包装
- 使用
http.HandlerFunc时,建议统一用中间件封装defer func() { if r := recover(); r != nil { log.Println(r) } }() - worker pool 类型代码中,每个 worker goroutine 都要自带 recover,否则一个 panic 会导致整个池静默退出
recover返回值类型是interface{},需类型断言判断panic原因
recover() 返回 interface{},不是字符串也不是 error。直接打印可能只看到 <nil> 或难读的内部表示,必须做类型检查才能区分是程序员主动 panic 的字符串、error 实例,还是其他类型。
典型写法:
if r := recover(); r != nil {
switch err := r.(type) {
case string:
log.Printf("panic: %s", err)
case error:
log.Printf("panic: %v", err)
default:
log.Printf("panic: unknown type %T, value %+v", err, err)
}
}
- 不要直接
log.Println(recover())—— 容易漏掉关键上下文 - 避免对所有 panic 一概 ignore:有些 panic(如 nil pointer dereference)说明代码有严重 bug,应记录堆栈并告警,而非静默吞掉
- 生产环境建议配合
debug.PrintStack()输出调用链,方便定位 panic 源头
真正难的不是写 recover,而是决定哪些 panic 值得兜底、哪些该让进程 crash 并触发监控告警——这需要结合业务语义和故障容忍边界来判断,不是加个 defer 就算兜住了。


















