如何在Golang中处理并发panic_Golang recover机制安全处理示例

recover 只能在引发 panic 的同一 goroutine 的 defer 函数中调用才有效;跨 goroutine 无法捕获,主 goroutine panic 会终止程序,子 goroutine panic 默认静默退出。

Go 的 recover 只能在 defer 中、且必须在引发 panic 的同一 goroutine 内调用才有效——跨 goroutine 的 panic 无法被其他 goroutine 的 recover 捕获。

goroutine 中的 panic 不会自动传播,也不会被外层 recover 拦截

这是最常踩的坑:启动一个新 goroutine 执行可能 panic 的逻辑,却在主 goroutine 里 defer + recover,结果毫无作用。

  • 每个 goroutine 有独立的调用栈,panic 只终止当前 goroutine
  • recover() 必须出现在 panic 发生的同一个 goroutine 的 defer 函数中
  • 主 goroutine panic 会导致整个程序退出;子 goroutine panic 默认静默终止(除非启用了 GODEBUG=panicnil=1 等调试标志)
go func() {
    defer func() {
        if r := recover(); r != nil {
            log.Printf("goroutine recovered: %v", r)
        }
    }()
    panic("boom in goroutine")
}()

使用 defer + recover 封装并发任务的标准写法

安全模式是:每个可能 panic 的 goroutine 自己负责 recover,而不是依赖外部统一捕获。

  • 避免在匿名 goroutine 外层套 defer —— 那个 defer 属于启动它的 goroutine,不是目标 goroutine
  • defer recover 逻辑写进 goroutine 内部,或封装为可复用函数如 safeGo
  • recover 后建议记录日志、清理资源(如关闭 channel、释放锁),但不要盲目“吞掉” panic
func safeGo(f func()) {
    go func() {
        defer func() {
            if r := recover(); r != nil {
                log.Printf("panic recovered in goroutine: %v", r)
                // 可选:上报指标、触发告警、写入错误追踪 ID
            }
        }()
        f()
    }()
}

// 使用
safeGo(func() {
    doRiskyWork() // 可能 panic
})

recover 不能恢复所有类型的崩溃

recover 只对 panic 有效,对运行时致命错误(如 nil pointer dereference、stack overflow、out of memory)无效——这些会直接终止程序。

立即学习“go语言免费学习笔记(深入)”;

  • nil 指针解引用、切片越界、map 写入 nil、channel 关闭 nil 等,在默认配置下会触发 runtime panic,recover 可捕获
  • 但若程序因 runtime.SetMaxStack 过小导致栈溢出,或 cgo 调用中发生 segfault,则无法 recover
  • os.Exit()syscall.Exit()、向已关闭 channel 发送值(非 panic 场景)等也不受 recover 影响

结合 context 控制并发 goroutine 的生命周期与 panic 后行为

recover 只解决“不崩溃”,但不解决“任务是否完成”“下游是否等待”等问题。真实场景中需配合 context 做协同退出。

  • recover 后,若该 goroutine 是某个长任务的一部分,应通过 ctx.Done() 通知上游放弃等待
  • 不要在 recover 后继续执行可能依赖已破坏状态的逻辑
  • 若 goroutine 持有锁或打开文件,recover 后务必显式释放,否则造成泄漏
func worker(ctx context.Context, ch <-chan int) {
    defer func() {
        if r := recover(); r != nil {
            log.Printf("worker panicked: %v", r)
            // 注意:此处不能直接 return,需确保资源清理
            // 如:close(outChan), mu.Unlock(), file.Close()
        }
    }()
    for {
        select {
        case <-ctx.Done():
            return
        case n := <-ch:
            process(n) // 可能 panic
        }
    }
}

真正难的不是写 recover,而是判断 panic 后的状态是否可信、要不要重试、下游能否容忍部分失败——这些没法靠语法机制自动解决。