defer + recover 在 goroutine 中必须置于其内部函数体中才能捕获 panic,因 recover 仅对同 goroutine 的 panic 有效;需用 channel 等机制向主 goroutine 传递错误信息。

为什么 defer + recover 在 goroutine 里不生效
直接在 goroutine 中写 defer recover() 是无效的——因为 recover() 只能在 panic 发生的同一 goroutine 中、且必须在 defer 函数中调用才起作用。如果没做这层封装,panic 会直接终止该 goroutine,且无法被外部捕获。
正确做法:在 goroutine 内部做完整的 panic 捕获
必须把 defer/recover 放进 goroutine 的函数体内部,且确保它包裹了可能 panic 的代码。常见错误是只在外层函数加 recover,却忘了每个 goroutine 是独立的执行栈。
示例:
go func() {
defer func() {
if r := recover(); r != nil {
log.Printf("goroutine panic: %v", r)
}
}()
// 这里放可能 panic 的代码,比如:
// panic("something went wrong")
// 或调用可能 panic 的第三方函数
}()
-
recover()必须在defer的匿名函数里调用,不能提前赋值或延迟调用 - 不要试图在
recover()后“继续执行”后续逻辑——它只负责兜底,不恢复执行流 - 如果 goroutine 是循环任务(如
for select {}),defer只在函数退出时触发,需确保每次迭代都受保护(通常把整个循环包进一个函数再启动 goroutine)
如何向主 goroutine 传递 panic 信息
单纯打印日志不够,有时需要通知主流程(比如终止服务、重试、降级)。推荐用 channel 传错,避免共享变量或全局状态。
立即学习“go语言免费学习笔记(深入)”;
示例:
errCh := make(chan error, 1)
go func() {
defer func() {
if r := recover(); r != nil {
var err error
switch x := r.(type) {
case string:
err = errors.New(x)
case error:
err = x
default:
err = fmt.Errorf("panic: %v", x)
}
errCh <- err
}
}()
// ...业务逻辑...
}()
// 主 goroutine 等待结果或超时
select {
case err := <-errCh:
log.Fatal("worker panicked:", err)
case <-time.After(5 * time.Second):
log.Println("worker completed normally")
}
- channel 容量至少为 1,防止 goroutine 因发送阻塞而卡死
- 别用
recover()返回的原始interface{}直接当error用,要显式类型判断 - 如果多个 goroutine 都可能 panic,建议每个配独立 channel 或用带标识的结构体
使用第三方库(如 errgroup)时的注意事项
像 errgroup.Group 这类工具默认不自动捕获 goroutine panic,它只收集 return error。一旦 goroutine panic,整个程序可能崩溃,errgroup.Wait() 也等不到结果。
- 必须手动包装每个
Go()的函数体,加上defer/recover - 不要依赖
errgroup自身“兜底”,它的设计目标不是处理 panic - 若用
golang.org/x/sync/errgroup,可参考其内部实现逻辑,但别假设它会拦截 runtime panic
真正难的不是写 recover,而是想清楚 panic 发生后该做什么:记录?重试?还是让进程退出?很多线上问题其实是 recover 了却没做有效响应,导致状态不一致。


















