go 的 panic 不跨 goroutine 传播,main 中的 recover 无法捕获其他 goroutine 的 panic;必须在每个 goroutine 内部单独使用 defer/recover,且需在 panic 前注册。

必须在每个 goroutine 内部单独加 defer/recover,外部包一层没用。
为什么 main 里的 recover 拦不住 goroutine 的 panic
Go 的 panic 不跨 goroutine 传播,主 goroutine 调用 recover() 对其他 goroutine 里发生的 panic 完全无效。这是最常被误判的一点——以为在 main() 函数开头写个 defer recover() 就能兜住所有并发异常。
实际效果是:panic 的 goroutine 直接退出,不崩溃进程,但可能漏日志、丢任务、状态不一致;主流程继续跑,仿佛什么都没发生。
- 错误写法:
go func() { panic("boom") }()外面包defer func() { recover() }() - 正确写法:每个
go启动的函数自己带defer+recover() -
recover()只在当前 goroutine 中有效,且必须在 panic 发生前已注册(即defer语句要写在panic之前)
如何安全地封装 recover 逻辑
重复写 defer func() { if r := recover(); r != nil { log.Printf("panic: %v", r) } }() 很容易漏或写错,建议封装成通用函数,比如 goSafe():
func goSafe(f func()) {
go func() {
defer func() {
if r := recover(); r != nil {
buf := make([]byte, 64
<p>使用时直接:<code>goSafe(func() { doSomethingRisky() })</code></p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/gongju/2525" title="Go语言(Golang)1.26.0"><img
src="https://img.php.cn/upload/manual/001/589/237/6a6adeed24a4a355.png" alt="Go语言(Golang)1.26.0" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/gongju/2525" title="Go语言(Golang)1.26.0" class="overflowclass">Go语言(Golang)1.26.0</a>
<p class="overflowclass">Go语言(Golang)1.26.0版本官方下载,版本号 1.26.0,适合旧项目维护、兼容性测试和指定版本开发环境搭建。</p>
</div>
<a rel="nofollow" href="/xiazai/gongju/2525" title="Go语言(Golang)1.26.0" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
- 务必在
recover()后立即return或通过 channel 通知,避免继续执行损坏状态的代码 - 不要在
recover()后继续使用可能已部分初始化或已释放的资源(如未关闭的*os.File、已 unlock 的sync.Mutex) - 堆栈信息用
runtime.Stack(buf, false)获取,false表示只抓当前 goroutine,避免阻塞
和 errgroup / WaitGroup 配合时的坑
用 errgroup.Group 或 sync.WaitGroup 管理并发时,panic 会导致等待逻辑卡死或结果丢失:
-
errgroup:裸go启动会绕过g.Go()的 error 收集机制,panic 被吞掉;必须用g.Go(func() error { ... })并确保内部有recover -
WaitGroup:panic 的 goroutine 如果没调用wg.Done(),wg.Wait()会永远阻塞;所以defer wg.Done()必须放在defer recover()外层或同一级 - 推荐写法:
go func() { defer wg.Done(); defer func() { ... }(); doWork() }()
recover 后要不要继续执行?
绝大多数情况下,recover() 后应该立刻 return,而不是试图“续上”业务逻辑。
原因很实在:panic 可能发生在任意位置,函数栈已展开,局部变量、指针、结构体字段可能处于中间态,继续执行大概率引发二次 panic 或数据竞争。
- 简单场景:
defer func() { if r := recover(); r != nil { log.Print(r); return } }() - 需要反馈结果时,用带缓冲的
chan error(如make(chan error, 1)),避免 goroutine 因发送阻塞而卡住 - 切忌在
recover()后调用可能再次 panic 的函数(比如又去读一个已 close 的 channel)
真正难的不是加 recover,而是判断哪些 goroutine 值得加、加在哪一层、recover 后怎么清理资源——这些都得结合业务路径来定,没法一劳永逸。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










