Go中收集goroutine错误的四种方式:1.带缓冲error通道;2.共享error变量+Mutex;3.errgroup.Group自动传播首个错误;4.锁保护切片汇总全部错误。

在Go语言中使用 WaitGroup 进行并发控制时,经常会遇到一个实际问题:如何在多个goroutine中收集错误并返回?因为每个goroutine无法直接返回值,所以需要额外的机制来传递错误信息。
1. 使用带缓冲的error通道收集错误
最常见且线程安全的方式是通过一个带缓冲的chan error来接收各个goroutine执行过程中的错误。WaitGroup确保所有任务完成后再读取错误。
示例代码:
func doWork(id int) error {
// 模拟工作,可能出错
if id == 3 {
return fmt.Errorf("工作协程 %d 失败", id)
}
return nil
}
func runWithErrors() error {
var wg sync.WaitGroup
errCh := make(chan error, 5) // 缓冲大小等于goroutine数量
for i := 0; i < 5; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
if err := doWork(i); err != nil {
errCh <- err
}
}(i)
}
wg.Wait()
close(errCh)
// 检查是否有任何错误
for err := range errCh {
return err // 返回第一个错误(或可改为收集所有)
}
return nil}
立即学习“go语言免费学习笔记(深入)”;
2. 使用共享error变量 + Mutex保护
如果只关心是否发生错误,或者只需要记录第一个错误,可以用一个全局error变量配合sync.Mutex进行同步。
示例:
func runWithSharedError() error {
var wg sync.WaitGroup
var mu sync.Mutex
var firstErr error
setErr := func(err error) {
mu.Lock()
defer mu.Unlock()
if firstErr == nil {
firstErr = err
}
}
for i := 0; i < 5; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
err := doWork(i)
if err != nil {
setErr(err)
}
}(i)
}
wg.Wait()
return firstErr}
立即学习“go语言免费学习笔记(深入)”;
3. 使用errgroup.Group替代原生WaitGroup
errgroup 是官方扩展包 golang.org/x/sync/errgroup 提供的增强型WaitGroup,专为错误传播设计,推荐用于需要简洁错误处理的场景。
特点:自动传播第一个返回的错误,并取消其他任务(如果使用了context)。
import "golang.org/x/sync/errgroup"func runWithErrGroup() error { var g errgroup.Group
for i := 0; i < 5; i++ { i := i g.Go(func() error { return doWork(i) }) } return g.Wait() // 返回第一个非nil错误}
立即学习“go语言免费学习笔记(深入)”;
4. 错误汇总模板:收集所有错误
有时你需要知道全部失败情况,而不是仅第一个错误。可以将所有错误收集到切片中。
type MultiError struct {
Errors []error
}
func (m *MultiError) Error() string {
var strs []string
for _, err := range m.Errors {
strs = append(strs, err.Error())
}
return strings.Join(strs, "; ")
}
func runAndCollectAllErrors() error {
var wg sync.WaitGroup
var mu sync.Mutex
var allErrors MultiError
for i := 0; i < 5; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
err := doWork(i)
if err != nil {
mu.Lock()
allErrors.Errors = append(allErrors.Errors, err)
mu.Unlock()
}
}(i)
}
wg.Wait()
if len(allErrors.Errors) > 0 {
return &allErrors
}
return nil}
立即学习“go语言免费学习笔记(深入)”;
基本上就这些常见的模式。选择哪种方式取决于你的需求:快速失败用 errgroup,需收集全部错误则用带锁的切片或通道汇总。关键是避免竞态,保证错误不丢失。










