在Golang的并发编程场景中,我们常常需要启动多个goroutine同时执行不同的任务,等所有任务执行完成后再把各自返回的结果汇总处理,这就是并发任务的结果聚合。合理的结果聚合方案既能发挥多goroutine的并发优势,又能保证结果的正确性和程序的稳定性。

基于channel和sync.WaitGroup的结果聚合方案
这是最基础也最常用的结果聚合方式,核心思路是使用sync.WaitGroup等待所有goroutine执行完成,同时用一个channel来收集各个goroutine返回的结果,最后从channel中读取所有结果完成聚合。
首先看具体实现代码:
package main
import (
"fmt"
"sync"
)
// 定义任务函数,返回任务结果
func task(id int, wg *sync.WaitGroup, resultChan chan<- int) {
defer wg.Done()
// 模拟任务执行耗时
// 这里实际场景可以是接口调用、数据计算等逻辑
result := id * 10
resultChan <- result
}
func main() {
// 定义任务数量
taskCount := 5
// 创建结果收集channel,缓冲大小设为任务数量避免阻塞
resultChan := make(chan int, taskCount)
var wg sync.WaitGroup
// 启动多个goroutine执行任务
for i := 0; i < taskCount; i++ {
wg.Add(1)
go task(i, &wg, resultChan)
}
// 启动一个goroutine等待所有任务完成,然后关闭channel
go func() {
wg.Wait()
close(resultChan)
}()
// 聚合所有结果
var totalResult int
for res := range resultChan {
totalResult += res
}
fmt.Printf("所有任务结果聚合后的总结果为:%dn", totalResult)
}
这段代码的逻辑很清晰,首先定义task函数作为单个并发任务,内部通过defer调用wg.Done()标记任务完成,然后把结果发送到resultChan中。主函数里先创建缓冲channel和WaitGroup,循环启动对应数量的goroutine,每个goroutine执行前调用wg.Add(1)。之后单独启动一个goroutine等待所有任务完成,再关闭channel,这样主函数里的for range读取channel时就不会阻塞,直到channel关闭后自动退出循环,最终得到聚合后的结果。
方案优缺点分析
- 优点:逻辑直观,容易理解,不需要引入额外的第三方库,适合大多数简单的并发结果聚合场景。
- 缺点:如果需要处理任务中的错误,或者需要控制goroutine的数量,需要额外添加更多逻辑,代码会变得稍显复杂。
使用errgroup实现带错误处理的并发结果聚合
如果我们的并发任务可能返回错误,并且希望某个任务出错时可以直接取消其他未完成的任务,那么使用官方的errgroup包会更合适,它封装了sync.WaitGroup的逻辑,同时支持错误传递和上下文取消。
下面是实现代码:
package main
import (
"context"
"fmt"
"golang.org/x/sync/errgroup"
)
// 带错误返回的任务函数
func taskWithErr(id int) (int, error) {
// 模拟任务执行,这里可以加入错误判断逻辑
if id == 3 {
return 0, fmt.Errorf("任务%d执行失败", id)
}
result := id * 10
return result, nil
}
func main() {
// 创建带取消的上下文
ctx := context.Background()
// 创建errgroup实例
eg, _ := errgroup.WithContext(ctx)
// 定义任务数量
taskCount := 5
// 创建结果切片,用于存储每个任务的结果
resultSlice := make([]int, taskCount)
// 启动并发任务
for i := 0; i < taskCount; i++ {
idx := i
eg.Go(func() error {
res, err := taskWithErr(idx)
if err != nil {
return err
}
resultSlice[idx] = res
return nil
})
}
// 等待所有任务完成,并获取错误
if err := eg.Wait(); err != nil {
fmt.Printf("并发任务执行出错:%vn", err)
return
}
// 聚合结果
totalResult := 0
for _, res := range resultSlice {
totalResult += res
}
fmt.Printf("所有任务结果聚合后的总结果为:%dn", totalResult)
}
这里使用errgroup的Go方法启动goroutine,每个任务返回错误时,errgroup会自动取消其他未执行的任务,主函数通过eg.Wait()获取最终的错误状态。结果存储在预先定义好的切片中,每个goroutine通过闭包捕获的索引对应到切片的对应位置,避免数据竞争问题。如果不需要处理错误,也可以使用不带上下文的errgroup,使用方式会更简单。
方案适用场景
- 适合需要错误处理、任务取消机制的并发场景,比如多个下游接口并发调用,某个接口报错后不需要再等待其他接口返回的情况。
- 不需要自己手动管理WaitGroup和channel的关闭逻辑,代码更简洁。
并发结果聚合的注意事项
在实际使用Golang做并发结果聚合时,有几个点需要特别注意:
- 结果收集的时候要避免多个goroutine同时写入同一个变量,不然会出现数据竞争,要么用channel传递结果,要么用互斥锁保护共享变量,要么像errgroup示例中那样用索引对应切片位置。
- 如果任务数量不确定或者任务数量很多,不要创建无缓冲的channel,不然发送结果的goroutine可能会阻塞,也不要创建过大的缓冲channel造成资源浪费,缓冲大小可以设为任务数量。
- 如果不需要等待所有任务完成,只需要获取第一个返回的结果,可以用select配合多个channel的方式,不需要等待所有goroutine执行完再做聚合。
并发结果聚合的核心是保证所有任务的结果都能被正确收集,同时避免程序出现阻塞或者数据错误,开发者可以根据实际的业务需求选择合适的实现方案。
Golang并发结果聚合goroutinechannelsync_WaitGroup修改时间:2026-06-28 10:27:30