Go 用 errgroup 管理并发子任务:错误收敛、取消传播与限流
Go 用 errgroup 管理并发子任务:错误收敛、取消传播与限流并发拉一批数据,你大概率写过这样的代码:开五个 goroutine 各自请求一个接口,用sync.WaitGroup等它们全部结束。跑起来没问题,直到有一天某个接口挂了——其他四个还在傻等,错误也没人收,日志里只留下一条超时。更糟的是,第一个失败之后剩下的请求已经没意义了,却还在白白消耗连接和 CPU。golang.org/x/sync/errgroup就是为解决这类问题而生的:它在 WaitGroup 的基础上,帮你收敛第一个错误、并在出错时自动取消其他子任务。这篇把它的三个实战用法讲透:错误收敛、取消传播、并发限流。先看朴素写法的问题用原生 WaitGroup 并发请求,想拿到「任意一个失败就返回错误」并不容易:funcfetchAll(urls[]string)error{varwg sync.WaitGroupvarmu sync.MutexvarfirstErrerrorfor_,u:rangeurls{wg.Add(1)gofunc(ustring){deferwg.Done()iferr:fetch(u);err!nil{mu.Lock()iffirstErrnil{// 只记第一个错误,得手动加锁firstErrerr}mu.Unlock()}}(u)}wg.Wait()returnfirstErr}问题一眼看不完:要手动加锁保护firstErr、要手动Add/Done、而且某个请求失败后其余请求不会停,continue 跑到底。这些样板代码每写一次都容易漏一个wg.Done()导致死锁。用 errgroup 收敛错误同样的逻辑,errgroup 一把梭:importgolang.org/x/sync/errgroupfuncfetchAll(urls[]string)error{varg errgroup.Groupfor_,u:rangeurls{u:u// 捕获循环变量(Go 1.22 前必须,之后可省)g.Go(func()error{returnfetch(u)// 直接返回 error,g 帮你收第一个})}returng.Wait()// 任意一个非 nil,Wait 就返回它}g.Go接收一个func() error,内部帮你管理 WaitGroup;g.Wait()会阻塞到所有子任务结束,并返回第一个非 nil 的错误。锁、计数器全省了。注意语义:Wait只返回第一个错误,后面的错误会被丢弃。如果你需要所有错误,得自己在闭包里收集(比如塞进一个带锁的 slice),errgroup 不替你做这件事。关键升级:出错就取消其他任务上面的版本虽然收了错,但失败后其余请求仍会跑完。真正的价值在errgroup.WithContext:它派生一个 context,任意子任务返回非 nil 错误时,自动 cancel 这个 context,其他子任务只要监听了 ctx 就能及时退出。funcfetchAll(ctx context.Context,urls[]string)([]string,error){g,ctx:errgroup.WithContext(ctx)// 派生可取消的 ctxresults:make([]string,len(urls))fori,u:rangeurls{i,u:i,u g.Go(func()error{// 把 ctx 透传给下游,失败时这里会随 ctx 一起被取消body,err:fetchWithCtx(ctx,u)iferr!nil{returnerr// 触发 ctx cancel,其他 goroutine 收到 Done}results[i]body// 各写各的下标,无需加锁returnnil})}iferr:g.Wait();err!nil{returnnil,err}returnresults,nil}funcfetchWithCtx(ctx context.Context,urlstring)(string,error){req,_:http.NewRequestWithContext(ctx,http.MethodGet,url,nil)resp,err:http.DefaultClient.Do(req)// ctx 被 cancel 时这里立刻返回 erroriferr!nil{return,err}deferresp.Body.Close()b,err:io.ReadAll(resp.Body)returnstring(b),err}这里有个容易忽略的细节:results[i] body每个 goroutine 写不同下标,读写不重叠,所以不需要加锁。这是并发写 slice 安全的少数场景之一——前提是长度预分配好、各 goroutine 下标互不相同。要让取消真正生效,子任务里的阻塞操作必须监听 ctx。HTTP 用NewRequestWithContext,数据库用QueryContext,自己的循环用select { case -ctx.Done(): return ctx.Err() ... }。如果下游不吃 ctx,cancel 就是一纸空文,任务照样跑到底。用 SetLimit 限流,别一次开几千个 goroutine如果 urls 有几千个,上面的写法会瞬间开几千个 goroutine 和连接,把下游打挂。Go 1.20 起 errgroup 内置了SetLimit,限制同时运行的子任务数:funcfetchAll(ctx context.Context,urls[]string)error{g,ctx:errgroup.WithContext(ctx)g.SetLimit(10)// 最多 10 个 goroutine 并发for_,u:rangeurls{u:u g.Go(func()error{// 超过 10 个时,g.Go 会阻塞直到有空位returnfetchWithCtx2(ctx,u)})}returng.Wait()}SetLimit(n)之后,g.Go在并发数达到上限时会阻塞,直到有子任务结束腾出名额。这等价于自己维护一个容量为 n 的信号量 channel,但代码干净得多。注意SetLimit必须在任何g.Go之前调用,中途改会 panic。如果你不想让g.Go阻塞,而是希望「没名额就跳过」,可以用TryGo,它返回 bool 表示是否成功启动。一个真实的坑:漏传 ctx 导致取消失效最常见的翻车是这样:用了WithContext拿到新 ctx,却在子任务里图省事用了外层的旧 ctx 或context.Background()。g,ctx:errgroup.WithContext(parentCtx)g.Go(func()error{// 错误:用了 parentCtx,errgroup 的 cancel 传不进来returnfetchWithCtx(parentCtx,u)})errgroup.WithContext返回的新ctx 才是挂了 cancel 钩子的那个。子任务必须用这个新 ctx,取消才会传播。记住口诀:g, ctx : errgroup.WithContext(...)之后,组内一律用左边这个新ctx。小结并发子任务要「收敛第一个错误」,别再手写 WaitGroup 锁,用errgroup.Group。需要「一个失败就停掉其他」时用errgroup.WithContext,并把返回的新 ctx透传给每个子任务的阻塞调用。任务数很大时用SetLimit(n)限流,避免 goroutine 与连接爆炸;要非阻塞就用TryGo。各 goroutine 写不同 slice 下标可以免锁,但共享变量仍要自己保护。一句话记忆点:errgroup WaitGroup 第一个错误 自动取消 限流,但取消能不能生效,全看你有没有把新 ctx 传下去。

相关新闻

深度探索Java字节码:5个实战技巧助你成为JVM专家

深度探索Java字节码:5个实战技巧助你成为JVM专家

深度探索Java字节码:5个实战技巧助你成为JVM专家 【免费下载链接】Krakatau Java decompiler, assembler, and disassembler 项目地址: https://gitcode.com/gh_mirrors/kr/Krakatau Krakatau是一款功能强大的Java字节码工具集,为开发者提供了直接…

2026/10/20 9:09:37 阅读更多 →
独立LLM应用开发:基于GPT-5.6-Sol的/goal功能完整实现指南

独立LLM应用开发:基于GPT-5.6-Sol的/goal功能完整实现指南

在构建独立LLM应用的过程中,如何让模型理解并执行复杂任务一直是开发者面临的挑战。DAIR.AI的Elvis Saravia提出的/goal功能设计,结合GPT-5.6-Sol模型的能力,为这一难题提供了创新解决方案。本文将完整拆解/goal功能的实现原理、环境搭建、代…

2026/10/20 9:09:44 阅读更多 →
OpenCore Legacy Patcher完整指南:让旧Mac焕发新生,轻松升级最新macOS

OpenCore Legacy Patcher完整指南:让旧Mac焕发新生,轻松升级最新macOS

OpenCore Legacy Patcher完整指南:让旧Mac焕发新生,轻松升级最新macOS 【免费下载链接】OpenCore-Legacy-Patcher Experience macOS just like before 项目地址: https://gitcode.com/GitHub_Trending/op/OpenCore-Legacy-Patcher 还在为你的老款…

2026/10/18 11:25:58 阅读更多 →

最新新闻

DeepSeek Agent框架插件化设计深度拆解:从硬编码到动态编排

DeepSeek Agent框架插件化设计深度拆解:从硬编码到动态编排

1. 从"能跑就行"到"想拆开看看":一个 Agent 框架到底值不值得深挖第一次看到 DeepSeek 开源 Agent 框架的消息时,我的反应和大多数人差不多——又一个 Agent 框架,能跑通 Demo 就行,没必要花时间研究内部实现…

2026/10/20 9:08:45 阅读更多 →
PAT甲级真题解析:字符串处理、排序与哈希统计的工程化思维

PAT甲级真题解析:字符串处理、排序与哈希统计的工程化思维

2018年冬季的PAT甲级考试里,7-2这道“Decode Registration Card of PAT”让我印象挺深。它不是那种让人挠头的高深算法题,反而像一份送到你面前的“信息管理综合题”,把字符串处理、排序规则、哈希统计这三个基本功全串在一起。说实话&#x…

2026/10/20 9:08:45 阅读更多 →
Selenium模拟用户操作实战:从环境搭建到稳定运行的完整指南

Selenium模拟用户操作实战:从环境搭建到稳定运行的完整指南

1. 先说清楚:为什么我们要在 Web 自动化里“模拟用户操作”如果你接触过 Selenium,那么“模拟用户常见操作”这几个字其实已经概括了 Selenium 最核心的价值——它不是在网页上做简单的网络请求,而是像真实用户一样,打开浏览器、点…

2026/10/20 9:08:45 阅读更多 →
服装品类趋势与消费者洞察数据分析可视化系统实战解析

服装品类趋势与消费者洞察数据分析可视化系统实战解析

1. 毕设选题定位:这个系统到底解决什么问题第一次听到“服装品类趋势及消费者洞察数据分析可视化系统”这个题目,可能觉得只是把一堆图表凑在一起,加上个前端页面,套个电商数据就算完事。实际上真正动手做起来会发现,这…

2026/10/20 9:08:45 阅读更多 →
从聊天助手到工程干将:Claude Code冠军配置的三大支柱与落地实践

从聊天助手到工程干将:Claude Code冠军配置的三大支柱与落地实践

在终端里用 Claude Code 干过几星期活的人,多半会经历一个落差:它讲起架构设计头头是道,可你真把一个完整需求丢过去,让它从头改到尾,它常常干到一半就卡住——不是停下来问"下一步怎么做",就是自…

2026/10/20 9:08:45 阅读更多 →
LabVIEW电源自动测试系统:状态机与采样链路设计实战

LabVIEW电源自动测试系统:状态机与采样链路设计实战

很多人对电源测试系统的第一印象是“高大上”,好像必须一堆仪器、一堆屏幕、一堆参数才叫完整。我见过不少同行搭出来的系统,机柜七八个U高,界面密密麻麻,绕线绕得跟圣诞树一样,最后测出来的数据却还要靠人肉核对。真正…

2026/10/20 9:07:44 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

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

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

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

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

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

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/19 10:02:04 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + 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/10/19 2:54:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

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

2026/10/19 9:10:22 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

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

2026/10/18 12:16:16 阅读更多 →