recover必须在defer中调用才有效,普通调用无效;需在每个goroutine内单独defer recover;应通过带缓冲channel统一收集panic错误;recover无法捕获Goexit、系统信号或Cgo崩溃;recover后禁止继续执行原逻辑,仅可记录错误和清理资源。
Go 的 recover 只有在 panic 正在发生、且当前 goroutine 尚未退出时才有效。如果写成普通函数调用(比如放在逻辑中间),它什么也抓不到。必须配合 defer 才能拦截 panic —— 这是绝大多数新手踩的第一个坑。
常见错误写法:
func handleRequest() {
recover() // ❌ 永远返回 nil
panic("something went wrong")
}
正确姿势是:
defer func() { recover() }()
go func() { ... }() 启动,recover 必须在该匿名函数内部 defer高并发下,成百上千个 goroutine 同时 panic,如果每个都直接打日志或写文件,容易触发 I/O 竞争甚至阻塞。更稳妥的做法是把错误信息发到一个带缓冲的 channel,由单独的“错误处理 goroutine”消费。
关键点:
make(chan error, 1000),防止生产者因满而阻塞for err := range ch,而不是单次 ,否则只处理第一个错误
示例结构:
var panicCh = make(chan error, 1000)func worker(id int) { defer func() { if r := recover(); r != nil { panicCh <- fmt.Errorf("worker %d panicked: %v", id, r) } }() // ... 实际业务逻辑,可能 panic }
// 单独启动一个 goroutine 处理 panic 日志 go func() { for err := range panicCh { log.Printf("PANIC captured: %v", err) } }()
recover 只对 panic 有效。以下情况它完全无能为力:
runtime.Goexit():主动终止当前 goroutine,不触发 panicSIGKILL、SIGQUIT
exit(1) 或发生段错误context.DeadlineExceeded 中断的 goroutine(本身不 panic)所以不能把 recover 当成“万能兜底”。真正健壮的服务需要:
context 控制超时和取消signal.Notify 捕获 SIGSEGV)有人会这么写:
defer func() {
if r := r
ecover(); r != nil {
log.Println("recovered:", r)
// 接着往下跑?❌
doNextStep() // 危险!栈已损坏,变量状态未知
}
}()
这是严重误区。panic 可能发生在任意位置,栈已部分展开,局部变量、锁、channel 状态都不可信。recover 后唯一安全的操作是:
继续执行后续业务代码,大概率引发二次 panic 或数据错乱。高并发场景下这种“带伤运行”的 goroutine 很难定位,比直接崩溃更麻烦。