应通过带缓冲的 channel 传递错误:创建容量为任务数上限的 chan error,各 goroutine 出错时写入 errCh,主 goroutine 统一接收;避免无缓冲 channel 阻塞或 defer close 导致 panic。

goroutine 中的错误怎么传出来,而不是直接 panic 或丢弃
Go 的 goroutine 无法像普通函数那样直接返回错误,一旦在 goroutine 里发生错误又没处理,就只能 log 或 panic——这会让主流程失去控制。正确做法是让每个 goroutine 把错误发到一个共享的 chan error,主 goroutine 统一接收并收集。
注意:不能用无缓冲 channel 直接写入,否则某个 goroutine 出错后阻塞,其他 goroutine 可能卡住或漏执行;也不能用 defer close(ch) 在 goroutine 里关 channel,会 panic。
- 用带缓冲的
errCh := make(chan error, len(tasks)),容量设为任务数上限 - 所有 goroutine 写入前检查
if err != nil { errCh ,不包装、不忽略 - 主 goroutine 在
waitGroup.Wait()后再关闭 channel,然后 range 读取
用 sync.WaitGroup + channel 收集错误的最小可靠模板
这是生产环境最常用、最不容易出错的组合。WaitGroup 确保所有 goroutine 完成,channel 负责错误传递,两者职责分明。
var wg sync.WaitGroup
errCh := make(chan error, len(tasks))
for _, task := range tasks {
wg.Add(1)
go func(t Task) {
defer wg.Done()
if err := t.Run(); err != nil {
errCh var errors []error
for err := range errCh {
errors = append(errors, err)
}
关键点:
- 闭包捕获变量要传参(
go func(t Task)),否则循环变量被复用,所有 goroutine 拿到的是最后一个值 -
close(errCh)必须在wg.Wait()之后,否则可能漏收错误 - 不要在 goroutine 里 close channel,Go 不允许向已关闭的 channel 发送数据
error 聚合后要不要区分来源?什么时候该用 map[string]error
如果多个 goroutine 执行不同逻辑(比如调用三个微服务),只汇总 error 列表很难定位是哪个环节失败。这时可以改用 map[string]error,key 是任务标识(如服务名、ID、URL)。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
但要注意:并发写 map 会 panic,必须加锁或换用 sync.Map(适合读多写少)或更简单的——每个 goroutine 发送 struct{ Key string; Err error },主 goroutine 统一构建 map。
- 推荐结构体方式:避免锁竞争,语义清晰,且可扩展字段(如耗时、重试次数)
- 如果 key 是固定字符串(如
"auth","payment"),且任务数不多,用sync.Mutex+ 普通 map 更直观 - 别用
errors.Join()直接拼接,它不保留上下文,调试时看不出哪个 goroutine 出错了
为什么 context.WithTimeout 配合 errgroup 更适合真实项目
纯 WaitGroup + channel 能工作,但缺超时、取消、错误快速返回(fail-fast)能力。标准库 golang.org/x/sync/errgroup 就是为此设计的:它内置了 context 支持,第一个 error 或 context.Done() 触发时自动取消其余 goroutine。
g, ctx := errgroup.WithContext(context.Background())
g.SetLimit(5) // 限流,防止并发过多
<p>for _, task := range tasks {
task := task // 闭包捕获
g.Go(func() error {
select {
case </p><p>if err := g.Wait(); err != nil {
// 至少一个 goroutine 返回了非 nil error
}
</p>
注意点:
-
errgroup.Group的Go方法本身会检查 context 是否已取消,所以内部不用重复 select -
SetLimit()控制并发数,避免瞬间起几百 goroutine 压垮下游 - 如果任务本身不支持 context(比如老代码没加
context.Context参数),就得自己包装一层,否则 cancel 不生效
真正容易被忽略的是:errgroup 的错误是“任意一个”失败就返回,不会等全部完成——这在需要全量结果的场景(比如批量校验)反而是缺点,得退回到 WaitGroup + channel 方案。










