Go 并发三板斧:如何优雅地收集多个 goroutine 的结果?

发布时间:2026/7/23 14:21:51

Go 并发三板斧:如何优雅地收集多个 goroutine 的结果? ### 写在前面在 Go 里写并发程序最常见的一个需求是**同时发起多个任务然后收集它们的结果。**但收集的方式不止一种。你是**全部等齐再处理**还是**谁先回来用谁的**还是**边回来边处理**这三种场景对应了 Go 并发编程里三种不同的模式。这篇文章带你一次性搞清楚。---## 场景设定假设我们有一个酒店后台系统需要同时查询三个数据源- 房价fetchPrice- 房态fetchRoomStatus- 客户偏好fetchPreference每个查询耗时 1-3 秒不等我们希望能用并发的方式加速。gotype QueryResult struct {Price stringRoom stringPrefer stringErr error}---## 模式一全部等齐再处理sync.WaitGroup这是最经典的用法**等所有任务都完成再统一返回。**gofunc queryAll(ctx context.Context, room string) QueryResult {var wg sync.WaitGroupvar mu sync.Mutexresult : QueryResult{}wg.Add(3)go func() {defer wg.Done()price, err : fetchPrice(ctx, room)mu.Lock()result.Price priceresult.Err errmu.Unlock()}()go func() {defer wg.Done()roomStatus, err : fetchRoomStatus(ctx, room)mu.Lock()result.Room roomStatusif err ! nil {result.Err err}mu.Unlock()}()go func() {defer wg.Done()prefer, err : fetchPreference(ctx, room)mu.Lock()result.Prefer preferif err ! nil {result.Err err}mu.Unlock()}()wg.Wait()return result}**特点**- 三个任务全部完成后wg.Wait() 才放行- 总耗时 最慢的那个任务- 适合需要完整数据的场景如生成报表、页面渲染---## 模式二谁先完成用谁的一次 select如果你只关心最快的那个结果可以用 select 取第一个回来的数据。gofunc queryAny(ctx context.Context, room string) QueryResult {ch : make(chan QueryResult, 3)go func() {price, err : fetchPrice(ctx, room)ch - QueryResult{Price: price, Err: err}}()go func() {roomStatus, err : fetchRoomStatus(ctx, room)ch - QueryResult{Room: roomStatus, Err: err}}()go func() {prefer, err : fetchPreference(ctx, room)ch - QueryResult{Prefer: prefer, Err: err}}()select {case result : -ch:return resultcase -ctx.Done():return QueryResult{Err: ctx.Err()}}}**特点**- 只取第一个返回的结果其余忽略- 总耗时 最快的那个任务- 适合竞争式查询如查多个缓存、多个供应商报价---## 模式三边完成边收集循环 select如果你既要**全部结果**又想**边完成边处理**可以用 for select 循环。gofunc queryAllWithSelect(ctx context.Context, room string) QueryResult {ch : make(chan QueryResult, 3)go func() {price, err : fetchPrice(ctx, room)ch - QueryResult{Price: price, Err: err}}()go func() {roomStatus, err : fetchRoomStatus(ctx, room)ch - QueryResult{Room: roomStatus, Err: err}}()go func() {prefer, err : fetchPreference(ctx, room)ch - QueryResult{Prefer: prefer, Err: err}}()result : QueryResult{}for i : 0; i 3; i {select {case part : -ch:if part.Price ! {result.Price part.Price}if part.Room ! {result.Room part.Room}if part.Prefer ! {result.Prefer part.Prefer}if part.Err ! nil result.Err nil {result.Err part.Err}case -ctx.Done():result.Err ctx.Err()return result}}return result}**特点**- 所有结果都能收到- 可以边收边处理如实时更新 UI 进度条- 总耗时 最慢的那个任务但不会卡死有超时保护---## 三种模式对比一览| 模式 | 工具 | 结果完整性 | 总耗时 | 适用场景 || :--- | :--- | :--- | :--- | :--- || 全部等齐 | sync.WaitGroup | ✅ 全部 | 最慢的任务 | 报表、完整页面 || 取最快 | 一次 select | ❌ 一个 | 最快的任务 | 缓存竞争、降级 || 边收边等 | 循环 select | ✅ 全部 | 最慢的任务 | 进度展示、实时更新 |---## 总结Go 的并发模型并不复杂但“收集结果”这个看似简单的需求其实有几种不同的实现方式。**选择哪个取决于你的业务需求**- 需要全部数据 用 WaitGroup- 只要最快的那一个 用 select- 全部都要还要实时反馈 用循环 select这三种模式你学会了吗

相关新闻