当前位置:首页 > 文章列表 > Golang > Go问答 > Go recover 为什么捕获不到另一个协程的 panic

Go recover 为什么捕获不到另一个协程的 panic

来源:17golang原创 2026-09-05 21:58:48 0浏览 收藏

很多 Go 程序第一次给并发任务加兜底时,会把 defer recover() 写在 main 或父函数里,然后发现 worker 里的 panic 仍然让程序退出。原因不是 recover 失效,而是它有严格的 goroutine 作用域:go worker() 建立了独立的控制栈,父 goroutine 的 defer 不会跨过去。要接住这个 panic,必须在 worker 入口安装一个直接调用 recover 的 deferred function。

记住一句话:panic 在哪条 goroutine 栈上发生,recover 就要在同一条栈的 deferred function 里调用;跨 goroutine 只能通过 channel、返回值或其他协作方式传递处理结果。
要点速览
  • 父函数的 recover 只能处理当前 goroutine 栈上的 panic。
  • worker 应由包装器统一 defer recover,并把 panic 值转换为 error。
  • 用 error channel 汇总异常,用 WaitGroup 负责生命周期,避免主流程提前退出或关闭通道过早。

先用一个最小例子看清作用域

下面的代码故意把 recover 放在父函数中。它只能保护 main 自己调用的同步代码,保护不了新 goroutine:

func main() {
    defer func() {
        fmt.Println("parent recover:", recover())
    }()

    go func() {
        panic("worker failed")
    }()
    time.Sleep(100 * time.Millisecond)
}

即使父函数还没有返回,worker 也已经在自己的控制栈上 panic。规范描述的恢复条件是“同一 goroutine 中发生 panic”,所以父函数的 deferred function 没有机会接管它。真实服务里不要用 sleep 等待结果,这里只用于暴露问题。

Go recover 同一 goroutine 作用域中主 goroutine、worker panic 与 deferred recover 的静态边界关系
图1:主 goroutine 与 worker goroutine 是两条独立控制栈,recover 只能落在发生 panic 的 worker 边界内。

把 recover 放进 worker 包装器

更稳妥的做法是规定:所有由业务代码启动的 worker,都从同一个包装器进入。recover 必须由这个包装器直接调用,不能再绕一层普通函数。

func safeGo(wg *sync.WaitGroup, errs chan

这里两个 defer 按后进先出执行:恢复逻辑先运行,随后 wg.Done() 标记 worker 结束。panic 被恢复后,safeGo 会正常返回,调用方收到的是一个普通 error。若 panic 值需要保留类型,可把 fmt.Errorf 换成项目自己的错误结构;不要把堆栈和敏感数据无条件写入日志。

Go safeGo 包装器将 worker panic 通过直接 deferred recover 转成 error channel 结果并由主流程验收
图2:safeGo 将 worker 的 panic 约束在自身 goroutine 内,再通过 error channel 和 WaitGroup 交给主流程验收。

用 channel 和 WaitGroup 汇总一个可验收的小项目

把包装器接到主流程时,关键是先登记 worker,再等待全部 worker 结束,最后关闭结果 channel:

func main() {
    var wg sync.WaitGroup
    errs := make(chan error, 2)

    jobs := []func(){
        func() { fmt.Println("job A ok") },
        func() { panic("job B failed") },
    }
    wg.Add(len(jobs))
    for _, job := range jobs {
        go safeGo(&wg, errs, job)
    }

    go func() {
        wg.Wait()
        close(errs)
    }()

    for err := range errs {
        fmt.Println("received:", err)
    }
    fmt.Println("all workers checked")
}

errs 设为有缓冲 channel,是为了让最后一个 worker 在收集循环尚未调度时也能交付错误;缓冲数量应按并发策略设计,不能当成无限队列。只有等待所有发送者结束后才能 close(errs),否则会出现 “send on closed channel”。验收标准也很具体:能看到 worker panic: job B failed,随后输出 all workers checked,进程不会因该业务 panic 直接终止。

三个容易让 recover 再次失效的边界

现象原因处理
父函数 recover 没反应panic 在另一个 goroutine把 defer recover 放到 worker 包装器
recover 返回 nil不在 panic 状态,或不是 deferred function 直接调用检查调用位置,避免在 helper 中间接调用
程序不退出但任务结果丢失只恢复了 panic,没有传递异常转为 error,通过 channel 或结果对象汇总

还要区分“可恢复的业务边界”和“程序不变量被破坏”。对单个任务输入异常,可以恢复并返回 error;共享状态损坏、严重运行时错误或无法保证继续工作的情况,不应仅靠 recover 掩盖。Go 1.21 起,直接调用 panic(nil) 也会触发非 nil 的运行时 panic,代码仍应把 recover 当作最后一道边界,而不是日常错误返回的替代品。

相关问题

recover 能不能在另一个函数里调用?

可以调用,但只有它本身就是直接被 defer 的函数时,才具备恢复语义。若 defer 的函数先调用 helper,再由 helper 调 recover,通常会得到 nil。

每个 goroutine 都要写一遍 recover 吗?

不必复制代码,把启动入口统一收口到 safeGo 之类的包装器即可。无法控制的第三方 goroutine 不能由你的父函数代为恢复。

recover 后还需要返回 error 吗?

需要。recover 只改变 panic 的控制流,不会自动告诉业务方任务失败;通过 error channel、结果结构或回调传递,主流程才能记录、重试或降级。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Redis 排行榜分数相同时怎么安排排序Redis 排行榜分数相同时怎么安排排序
上一篇
Redis 排行榜分数相同时怎么安排排序
Go 查询数据库遇到 NULL 怎么接收
下一篇
Go 查询数据库遇到 NULL 怎么接收
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    572次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    616次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    646次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    605次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    422次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码