子协程 panic 不会传播但会导致进程崩溃,必须在每个 goroutine 内部用 defer+recover 捕获;safeGo 需类型判断 panic 值并打印堆栈;errgroup.Group 不自动 recover,须手动包装;chan error 需合理设置缓冲并及时关闭。

子协程里的 panic 不会自动传播到主协程,但会导致整个进程退出——这不是“静默失败”,而是“看似运行中、实则已崩溃”的陷阱。
为什么 defer+recover 必须写在 goroutine 内部
主协程的 defer 对子协程完全无效,recover() 只能捕获当前 goroutine 的 panic。常见错误是只在 main() 里加一层 defer,结果子协程一 panic 还是直接退出进程。
-
recover()必须出现在触发panic的同一个 goroutine 中,且必须在defer函数体内调用 - 如果 goroutine 启动后没做
defer+recover,哪怕只漏一个,就可能让服务在半夜挂掉 - 注意:空的
recover()调用(比如写在go func() { recover(); }())没用,它必须在defer里,且 panic 发生在该 defer 所在函数栈内
safeGo 封装时必须处理 panic 值类型和堆栈
直接 fmt.Printf("panic: %v", r) 容易掩盖问题:接口值 r 可能是 string、error 或自定义结构体,不判断类型就打印,日志里只看到 &{...} 之类无法定位的内容。
- 建议用
switch r := r.(type)分支处理,对error类型调用r.Error(),对string直接输出,其他类型用fmt.Sprintf("%+v", r) - 加上
debug.Stack()获取堆栈,但注意它返回的是[]byte,需转成string;生产环境建议限制长度(如取前2048字节),避免日志爆炸 - 不要在
recover后忽略资源清理:如果 panic 前打开了文件或持有了锁,recover 后得手动close或unlock,否则泄漏
errgroup.Group 不能自动 recover,必须手动包装
errgroup.Group 本身不包含 recover 逻辑,传给 eg.Go() 的函数一旦 panic,照样导致进程退出——这是线上事故高频点。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- 正确做法是把业务函数先用
safeGo包一层,再传给eg.Go(),例如:eg.Go(func() error { safeRun(func() { ... }) }) - 若业务函数本身返回
error,别在eg.Go()外层再套go,否则会绕过errgroup的错误收集机制 -
errgroup的eg.Wait()返回的是第一个非 nil error,不代表所有 goroutine 都成功了;想确认是否全部完成,得结合runtime.NumGoroutine()或埋点统计
chan error 通信时缓冲区大小和关闭时机很关键
用 chan error 接收子协程错误时,不设缓冲或忘记 close,主协程就会卡死在 上,整个流程 hang 住。
- 必须用带缓冲的 channel:
errCh := make(chan error, 1),避免 goroutine 在发送时阻塞(尤其当主协程还没开始接收) - 务必在 goroutine 结束前发一次值(哪怕是
nil),并调用close(errCh),否则主协程用for range errCh会永远等下去 - 不要在 goroutine 内部
recover后吞掉 error:recover 只是拦截 panic,不代表问题已解决;该返回的 error 还是要通过 channel 传出去
最常被忽略的一点:recover 后的 goroutine 状态是“已终止”,它的堆栈、局部变量、未释放的资源(timer、channel、mutex)不会自动清理。你得自己判断是否需要重试、降级或上报监控——panic 捕获只是止损第一步,不是善后终点。

















