recover必须在当前goroutine的defer函数中调用才有效,直接调用或跨goroutine调用均返回nil;它仅捕获本协程内未被其他recover处理的panic,且捕获后函数立即return,不恢复执行流。

为什么不能直接用 recover() 捕获 goroutine 的 panic
因为 recover() 只在当前 goroutine 的 defer 链中有效,且必须在 panic 发生的同一 goroutine 中调用。启动新 goroutine 后,它的调用栈和 defer 链完全独立,主 goroutine 的 recover() 对它毫无作用。
常见错误是这样写:
go func() {
// 这里 panic,但外面 recover 不到
panic("boom")
}()
defer func() {
if r := recover(); r != nil { // 永远不会触发
log.Println("caught:", r)
}
}()
结果就是 panic 未被捕获,程序崩溃或输出 fatal error: all goroutines are asleep - deadlock(如果还等它结束的话)。
标准做法:每个 goroutine 内部加 defer + recover()
最直接、最可控的方式,就是在每个需保护的 goroutine 函数体开头就安排 defer 和 recover()。
- 必须在 goroutine 启动前就定义好带 recover 的闭包,不能事后补
- 推荐封装成工具函数,避免重复代码
- 注意:recover 后程序继续运行,但该 goroutine 已退出,别试图“恢复执行”
示例:
func safeGo(f func()) {
go func() {
defer func() {
if r := recover(); r != nil {
log.Printf("goroutine panicked: %v", r)
}
}()
f()
}()
}
<p>// 使用
safeGo(func() {
time.Sleep(100 * time.Millisecond)
panic("in goroutine")
})</p>
用 sync.WaitGroup + 错误通道统一收集 panic 信息
当需要批量启动 goroutine 并汇总所有 panic 错误时,光靠日志不够——你可能想做失败重试、统计或上报。这时要配合 sync.WaitGroup 和一个带缓冲的 chan error。
- 缓冲区大小建议设为预期 goroutine 数量,避免发送阻塞导致 goroutine 卡住
- 务必在 goroutine 内关闭 channel 或用
sync.Once控制,否则主 goroutinerange会永远等待 - 不要在 recover 后往同一个无缓冲 channel 发送,极易死锁
示例:
errCh := make(chan error, 10)
var wg sync.WaitGroup
<p>for i := 0; i </p><p>go func() {
wg.Wait()
close(errCh)
}()</p><p>for err := range errCh {
log.Println("error:", err)
}</p>
进阶:用第三方库 errgroup.Group 简化管理
golang.org/x/sync/errgroup 提供了带错误传播和上下文取消的 goroutine 批量控制能力,但它本身不自动 recover panic——你需要手动包装。
- 它只捕获显式返回的
error,对 panic 仍需自己 defer recover - 适合已习惯用
errgroup做并发控制的项目,避免混用多种模式 - recover 后建议把 panic 转成 error 返回,以便
eg.Wait()统一处理
示例:
eg, ctx := errgroup.WithContext(context.Background())
for i := 0; i <p>真正容易被忽略的是 panic 的类型多样性:<code>recover()</code> 返回的是 <code>interface{}</code>,可能是 <code>string</code>、<code>error</code>、自定义 struct,甚至 <code>nil</code>(比如调用了 <code>runtime.Goexit()</code>)。不做类型断言直接打印,可能得到 <code><nil></nil></code> 或难读的内存地址。线上环境建议统一转成字符串并带上堆栈,用 <code>debug.PrintStack()</code> 或 <code>runtime/debug.Stack()</code> 补充上下文。</p>golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











