多个goroutine并发时错误不能靠return传递,需用errgroup.Group统一收集首个错误或手动用chan error+sync.WaitGroup聚合所有错误,注意循环变量捕获、缓冲通道、正确关闭和上下文取消。
Go 中 goroutine 是异步的,go func() {...}() 启动后立即返回,主 goroutine 不会等待它结束,更无法直接捕获其 return 的错误。想让多个 g
oroutine 的错误“回传”给调用方,必须显式设计通信路径。
errgroup.Group 统一收集并传播第一个错误标准库 golang.org/x/sync/errgroup 是最常用、最稳妥的选择。它内部基于 sync.WaitGroup 和 chan error,自动处理“任意一个出错就取消其余任务”的常见需求。
使用要点:
errgroup.Group 的 Go 方法接收 func() error,不是无参函数;返回非 nil 错误时,会自动取消其他正在运行的 goroutine(需配合 ctx)Wait() 才能获取最终错误;若未出错,返回 nil
import (
"context"
"fmt"
"golang.org/x/sync/errgroup"
)
func fetchAll(ctx context.Context) error {
g, ctx := errgroup.WithContext(ctx)
urls := []string{"https://a.com", "https://b.com", "https://c.com"}
for _, url := range urls {
url := url // 避免循环变量复用
g.Go(func() error {
return doFetch(ctx, url) // 返回 error
})
}
return g.Wait() // 阻塞直到全部完成或首个 error 返回}
立即学习“go语言免费学习笔记(深入)”;
手动用 chan error + sync.WaitGroup 更灵活但易出错
当需要自定义错误聚合逻辑(比如收集所有错误、忽略某些类型),可手动管理通道和等待组。但要注意几个硬伤:
chan error,否则某个 goroutine 出错后写入阻塞,导致其他 goroutine 无法退出close(ch),必须由主 goroutine 在 Wait() 后关闭,否则 panicfunc fetchAllManual() []error {
var wg sync.WaitGroup
ch := make(chan error, 10) // 缓冲大小 ≥ goroutine 数量
urls := []string{"https://a.com", "https://b.com"}
for _, url := range urls {
wg.Add(1)
go func(u string) {
defer wg.Done()
if err := doFetch(context.Background(), u); err != nil {
ch <- err
}
}(url)
}
go func() {
wg.Wait()
close(ch)
}()
var errs []error
for err := range ch {
errs = append(errs, err)
}
return errs}
立即学习“go语言免费学习笔记(深入)”;
别踩这些坑
实际写的时候,这几个点最容易翻车:
url := url),导致所有 goroutine 用的是最后一个值chan error,一旦有错误就卡死,整个程序 hang 住panic 却没 recover,错误完全丢失,且可能 crashselect + default 非阻塞读 channel,漏掉部分错误context.WithTimeout 的 cancel() 放在 goroutine 里调用,导致主流程无法控制超时复杂点在于:错误传播策略(立即失败 vs 全部尝试)和上下文取消的耦合度很高,选错模式会导致调试困难或资源泄漏。动手前先想清楚——你到底要“尽快止损”,还是“尽力完成”。