首页/新闻资讯/正文详情

WaitGroup与errgroup:并发任务编排

发布时间:2026/9/26 11:00:50 来源:云帆数科 栏目:资讯中心
WaitGroup与errgroup:并发任务编排
WaitGroup与errgroup并发任务编排sync.WaitGroup是并发编程的标配errgroup在其之上增加了错误传播和取消语义。从WaitGroup的计数器语义Add/Done/Wait到errgroup的WithContext取消本文讲透并发任务编排的底层机制与最佳实践。一、核心技术知识点讲解1.1 WaitGroup的底层结构typeWaitGroupstruct{noCopy noCopy state atomic.Uint64// 高32位计数器低32位等待者数semauint32// 信号量}关键点计数器高32位Add/Done增减等待者数低32位Wait的goroutine数信号量Wait阻塞和唤醒机制1.2 Add/Done/Wait语义varwg sync.WaitGroup wg.Add(1)// 计数器1gofunc(){deferwg.Done()// 计数器-1}()wg.Wait()// 阻塞直到计数器为0Add必须在Wait之前或并发安全地调用计数器为正时Done等价于Add(-1)计数器不能为负panic: negative WaitGroup counter1.3 WaitGroup的陷阱Add与Wait的竞争不能在Wait的同时Add未使用race检测可能panicDone次数过多panic复制WaitGroup禁止复制noCopy标记go vet检测Wait之后重用需确保计数器归零后才能安全重用1.4 errgroup.Groupimportgolang.org/x/sync/errgroupvarg errgroup.Group g.Go(func()error{// 任务returnnil})iferr:g.Wait();err!nil{// 处理错误}第一个返回错误的goroutine会取消其他任务WithContext自动传播第一个非nil错误限制并发数g.SetLimit(n)1.5 errgroup.WithContextg,ctx:errgroup.WithContext(ctx)g.Go(func()error{select{case-ctx.Done():returnctx.Err()// 其他goroutine出错取消default:}// 业务returnnil})1.6 编排模式对比工具用途错误传播取消WaitGroup简单等待无无errgroup错误传播取消第一个错误WithContexterrgroupSetLimit并发限制第一个错误支持channel自定义编排手动手动二、实战代码演示2.1 WaitGroup基础packagemainimport(fmtsynctime)funcmain(){varwg sync.WaitGroupfori:0;i5;i{wg.Add(1)// 在主goroutine中Addgofunc(iint){deferwg.Done()time.Sleep(time.Duration(i)*100*time.Millisecond)fmt.Printf(Task %d done\n,i)}(i)}wg.Wait()fmt.Println(All tasks done)}2.2 WaitGroup的Add时机packagemainimport(fmtsynctime)funccorrectAdd(){varwg sync.WaitGroup jobs:[]int{1,2,3,4,5}wg.Add(len(jobs))// 统一Addfor_,j:rangejobs{gofunc(jobint){deferwg.Done()time.Sleep(10*time.Millisecond)fmt.Printf(Processed %d\n,job)}(j)}wg.Wait()fmt.Println(Done)}funcmain(){correctAdd()}2.3 errgroup基础packagemainimport(errorsfmttimegolang.org/x/sync/errgroup)funcmain(){varg errgroup.Group g.Go(func()error{time.Sleep(100*time.Millisecond)fmt.Println(Task 1 done)returnnil})g.Go(func()error{time.Sleep(200*time.Millisecond)fmt.Println(Task 2 done)returnnil})g.Go(func()error{time.Sleep(50*time.Millisecond)returnerrors.New(task 3 failed)})err:g.Wait()iferr!nil{fmt.Printf(Error: %v\n,err)}else{fmt.Println(All succeeded)}}2.4 errgroup.WithContext取消packagemainimport(contexterrorsfmttimegolang.org/x/sync/errgroup)funcmain(){g,ctx:errgroup.WithContext(context.Background())g.Go(func()error{select{case-ctx.Done():fmt.Println(Task 1 cancelled:,ctx.Err())returnctx.Err()case-time.After(500*time.Millisecond):fmt.Println(Task 1 completed)returnnil}})g.Go(func()error{time.Sleep(100*time.Millisecond)returnerrors.New(task 2 failed fast)})g.Go(func()error{select{case-ctx.Done():fmt.Println(Task 3 cancelled:,ctx.Err())returnctx.Err()case-time.After(3*time.Second):fmt.Println(Task 3 completed)returnnil}})err:g.Wait()fmt.Printf(Final error: %v\n,err)}2.5 生产级并行API聚合packagemainimport(contextfmttimegolang.org/x/sync/errgroup)typeProfilestruct{UserIDintNamestringOrders[]stringFollowersint}funcfetchUserName(ctx context.Context,idint)(string,error){time.Sleep(50*time.Millisecond)returnfmt.Sprintf(user-%d,id),nil}funcfetchOrders(ctx context.Context,idint)([]string,error){time.Sleep(80*time.Millisecond)return[]string{order-1,order-2},nil}funcfetchFollowers(ctx context.Context,idint)(int,error){time.Sleep(30*time.Millisecond)return1000,nil}funcgetProfileParallel(ctx context.Context,userIDint)(*Profile,error){g,ctx:errgroup.WithContext(ctx)profile:Profile{UserID:userID}g.Go(func()error{name,err:fetchUserName(ctx,userID)iferr!nil{returnerr}profile.Namenamereturnnil})g.Go(func()error{orders,err:fetchOrders(ctx,userID)iferr!nil{returnerr}profile.Ordersordersreturnnil})g.Go(func()error{followers,err:fetchFollowers(ctx,userID)iferr!nil{returnerr}profile.Followersfollowersreturnnil})iferr:g.Wait();err!nil{returnnil,err}returnprofile,nil}funcmain(){start:time.Now()p,err:getProfileParallel(context.Background(),42)iferr!nil{fmt.Println(Error:,err)return}fmt.Printf(Profile: %v (took %v)\n,p,time.Since(start))}2.6 errgroup并发限制packagemainimport(contextfmtsynctimegolang.org/x/sync/errgroup)funcmain(){g,_:errgroup.WithContext(context.Background())g.SetLimit(3)varmu sync.Mutexvaractiveintfori:0;i10;i{i:i g.Go(func()error{mu.Lock()activecurrent:active mu.Unlock()fmt.Printf(Task %d starting (active: %d)\n,i,current)time.Sleep(200*time.Millisecond)mu.Lock()active--mu.Unlock()fmt.Printf(Task %d done\n,i)returnnil})}iferr:g.Wait();err!nil{fmt.Println(Error:,err)}fmt.Println(All done)}三、开发痛点与报错避坑指南3.1 复制WaitGroupfuncprocess(wg sync.WaitGroup){// ❌ 值传递复制deferwg.Done()}避坑方案用指针传递funcprocess(wg*sync.WaitGroup){deferwg.Done()}3.2 negative WaitGroup countervarwg sync.WaitGroup wg.Add(1)wg.Done()wg.Done()// panic: negative WaitGroup counter避坑方案每个Add对应恰好一个Done用defer保证。3.3 Wait与Add竞争痛点描述goroutine内Add主goroutine Wait可能发生Wait先执行完计数器为0然后Add导致goroutine没被等待。避坑方案所有Add在主goroutine中完成或Add在goroutine启动前完成。3.4 errgroup的取消语义误解痛点描述认为g.Wait返回错误后所有任务立即停止。实际上只有使用ctx.Done()检查的任务才会响应取消。避坑方案任务中监听ctx.Done()纯计算任务不会被打断。3.5 SetLimit与死锁痛点描述SetLimit(n)太小且任务内再启动g.Go可能死锁等待goroutine占满限制。避坑方案内部任务用独立group或限制1且任务不自旋。四、全文总结WaitGroup结构计数器等待者数信号量atomic实现。使用准则Add在主goroutine、Done用defer、禁止复制。errgroup增强错误传播WithContext取消SetLimit并发限制。取消机制第一个错误触发ctx取消任务需监听ctx.Done()。应用场景并行API聚合、批量任务、优雅关闭。对比选择简单等待用WaitGroup需要错误/取消用errgroup。五、技术进阶展望泛型errgroup未来可能提供泛型版本的返回值聚合。结构化并发conc库的WaitGroup增强panic捕获、结果收集。限流集成errgroup.SetLimit与令牌桶结合的并发控制。编排模式库flow、sema等库提供更丰富的编排原语。六、参考文献Go官方文档 - sync.WaitGroup: https://pkg.go.dev/sync#WaitGroupGo源码 - sync/waitgroup.go: https://github.com/golang/go/blob/master/src/sync/waitgroup.goerrgroup文档: https://pkg.go.dev/golang.org/x/sync/errgroupGo Blog - Pipelines and cancellation: https://go.dev/blog/pipelinessourcegraph/conc: https://github.com/sourcegraph/conc

相关推荐

Go 加密与 TLS:从 RSA 到 ECDHE 全面实战
Go 加密与 TLS:从 RSA 到 ECDHE 全面实战

Go 加密与 TLS:从 RSA 到 ECDHE 全面实战加密与 TLS 是安全工程的基础。本文讲清 hash、对称/非对称加密、TLS 握手与现代最佳实践。一、Hash 函数 import "crypto/sha256"sum : sha256.Sum256([]byte("hello")) fmt.Printf("%x", su… · 2026/9/26 11:00:50

•博图交叉参考的隐藏用法,找变量、查地址快10倍
•博图交叉参考的隐藏用法,找变量、查地址快10倍

程序大了,找一个变量用在哪里、有没有重复、地址对不对,翻程序要半天。交叉参考功能,一键就能查清楚。1. 查变量使用位置操作步骤:(1)右键变量,选择“交叉引用”(2)所有用… · 2026/9/26 11:00:50

第05篇-模型切换与Provider管理:用TaoToken统一Key打通Hermes Agent多模型路由
第05篇-模型切换与Provider管理:用TaoToken统一Key打通Hermes Agent多模型路由

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 11:00:43

用自然语言玩转 AI 原生数据库 —— seekdb MCP Server 配 TaoToken 实战
用自然语言玩转 AI 原生数据库 —— seekdb MCP Server 配 TaoToken 实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 12:08:19

JVM调优实战:从Full GC排查到内存泄漏根因分析
JVM调优实战:从Full GC排查到内存泄漏根因分析

三年前的一次线上事故,让我对JVM调优的态度从“面试背题”变成了“保命技能”。当时业务高峰刚起来,接口超时率突然飙到20%以上,监控面板上Full GC频率从半小时一次直接变成每分钟好几次,CPU瞬间被打满。重启服务只能撑十几分钟&a… · 2026/9/26 12:08:19

Superpowers + gstack 应用实践指南:TaoToken 统一 Key 接入 Claude Code 插件配置
Superpowers + gstack 应用实践指南:TaoToken 统一 Key 接入 Claude Code 插件配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 12:08:18

PowerShell批量转码实战:ANSI/GBK一键转UTF-8告别乱码
PowerShell批量转码实战:ANSI/GBK一键转UTF-8告别乱码

接手旧项目时最烦什么?我最烦编码乱码。前几天在Win11上处理一套老管理系统导出的文本文件,打开一看满屏的锟斤拷和问号,典型的ANSI编码文件被按UTF-8解码了。单独改一个文件不难,可文件夹里躺着几百个,要一个个用编辑… · 2026/9/26 12:08:12

Ubuntu装向日葵全攻略:从依赖管理到黑屏排查
Ubuntu装向日葵全攻略:从依赖管理到黑屏排查

Ubuntu装向日葵这个事儿,说难不难,说简单也不简单。我见过太多人在Windows上装向日葵闭眼下一步就完事儿了,结果换到Ubuntu上一顿操作猛如虎,最后卡在依赖报错或者界面起不来,一脸懵。这篇文章我想把整个流程掰开揉碎了… · 2026/9/26 12:08:12

Trae AI IDE 接入 TaoToken 统一 Key:DeepSeek R1/V3 与豆包大模型1.5 自由切换配置指南
Trae AI IDE 接入 TaoToken 统一 Key:DeepSeek R1/V3 与豆包大模型1.5 自由切换配置指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 12:08:11

数据库课后习题答案别硬背:当测试用例集刷,效率翻倍
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍

简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21

OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40

向下兼容与向上兼容:接口设计中的兼容性策略与工程实践
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践

一次版本升级事故,是很多团队绕不过去的坎。线上环境里,服务端明明已经上线了新版接口,老的移动端还在照着旧文档传参数。请求一到网关,校验直接拒绝,用户操作失败,客服群炸了锅,开发群里开始互… · 2026/9/26 0:00:46

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码