errgroup.WithContext本身不支持并发数限制,需用带缓冲channel实现信号量控制最大并发数,再与errgroup协作完成错误聚合与等待。

errgroup.WithContext 本身不支持并发数限制
直接用 errgroup.WithContext 创建的组,所有 Go 调用默认是**无限制并发**的——它只负责错误传播和等待,不是限流器。想控制最大同时运行的 goroutine 数量,必须自己加一层调度逻辑。
常见错误现象:errgroup.Go 被循环调用 1000 次,结果瞬间起 1000 个 goroutine,内存暴涨或下游服务被打挂。
- 正确做法是用带缓冲的 channel 控制“许可”数量,每个任务先从 channel 取一个 token 再执行
- 不要试图给
errgroup.Group加字段或封装新方法来“增强”,它设计上就不管并发控制 - 别混淆
semaphore(信号量)和errgroup的职责:前者管“能不能跑”,后者管“跑完怎么收尾”
用 buffered channel 实现并发限制 + errgroup 协作
这是最轻量、最符合 Go 哲学的做法:channel 做限流,errgroup 做错误聚合与等待。两者组合干净利落,无需引入额外依赖。
使用场景:批量调用 HTTP 接口、并行读文件、数据库批量查询等需要可控并发 + 统一错误处理的场合。
立即学习“go语言免费学习笔记(深入)”;
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
- 创建容量为 N 的
chan struct{}作为信号量,N 就是最大并发数 - 每个任务函数开头
(阻塞获取许可),结尾 <code>sem (归还许可) - 把任务包装成闭包传给
eg.Go,确保defer或显式归还不会漏掉
sem := make(chan struct{}, 5) // 最大并发 5
eg, ctx := errgroup.WithContext(context.Background())
<p>for _, item := range items {
item := item // 防止循环变量捕获
eg.Go(func() error {
sem <- struct{}{} // 获取许可
defer func() { <-sem }() // 归还许可,用 defer 更安全</p><pre class="brush:php;toolbar:false;"> select {
case <-ctx.Done():
return ctx.Err()
default:
}
return doWork(ctx, item)
})}
if err := eg.Wait(); err != nil { // 处理错误 }
为什么不用 golang.org/x/sync/semaphore?
虽然 golang.org/x/sync/semaphore 功能更完整(支持带权、可取消的 acquire),但对简单并发限制来说,它反而容易踩坑:
- 它的
Acquire是阻塞且可能被 context 取消的,如果没处理好ctx.Err(),会导致 goroutine 泄漏 - 返回的
func()必须显式调用释放,漏掉就死锁;而 channel 方式靠defer更难出错 - 性能差异在绝大多数业务场景下可忽略,
chan struct{}的 runtime 优化足够好 - 多一个依赖,就多一个版本兼容和构建问题,尤其在 vendor 或 air-gapped 环境里
注意 context 取消时机和 errgroup 的交互
errgroup 会响应第一个返回的非-nil 错误,也会响应 context 取消。但如果你的任务里没主动检查 ctx.Err(),即使 context 已取消,goroutine 仍可能继续跑完——这不是 errgroup 的 bug,而是你没配合它。
- 所有耗时操作前都该加
select { case 或用 <code>ctx构造带超时的子请求 - 不要只在最后返回错误时才检查
ctx.Err(),中间 IO、sleep、重试都要感知取消 - 如果某个任务 panic,errgroup 会把它转成
errors.New("panic"),但不会中断其他正在运行的任务——这点和 channel 限流无关,是 errgroup 的设计行为
实际写的时候,真正容易被忽略的是:信号量 channel 的容量设太小,导致整体吞吐上不去;设太大,又失去限流意义。这个值得结合下游服务的承受能力、单次任务平均耗时、以及你的 P95 延迟目标反复压测调整,不能只拍脑袋定 10 或 100。

















