当前位置:首页 > 文章列表 > Golang > Go问答 > 并发计数复用

并发计数复用

来源:17golang原创 2026-11-11 16:30:11 0浏览 收藏

先说结论:sync.WaitGroup 可以复用,但复用不是把计数器“清零”后马上继续写。上一轮任务必须已经完成,上一轮的所有 Wait 调用也必须已经返回,之后才能为下一组独立任务重新调用正向 Add。

判断一个复用写法是否安全,可以只看三件事:本轮是否在启动任务前建立了计数、本轮每个任务是否恰好调用一次 Done、下一轮的 Add 是否发生在上一轮所有 Wait 返回之后。下面按这个边界拆开说明。

先把一轮任务的计数归零

WaitGroup 维护的是当前这一轮仍未完成的任务数。正向 Add 建立计数,任务结束时用 Done 归还一个计数,Wait 则等待计数回到零。计数为零时,从零开始的正向 Add 必须先于对应的 Wait。

package main

import "sync"

func runBatch(tasks []func()) {
	var wg sync.WaitGroup

	// 先建立本轮任务总数,再启动 goroutine,避免 Wait 先看到零。
	wg.Add(len(tasks))
	for _, task := range tasks {
		current := task
		go func() {
			defer wg.Done() // 每个任务结束时只归还一次计数。
			current()
		}()
	}

	// 当前轮计数归零后,Wait 才会返回。
	wg.Wait()
}
WaitGroup 中任务集合、Add、goroutine、Done 和 Wait 的静态关系说明图
图1:结构说明图,查看当前轮任务集合与 Add、Done、Wait 的静态关系;这不是运行截图。

这里的顺序很重要。把 Add 放进新启动的 goroutine,会让 Add 与 Wait 竞争:如果 Wait 先观察到计数为零,它可能提前返回,外层代码就会误以为本轮已经结束。即使偶尔运行正确,也不能把这种时序交给调度器碰运气。

复用的真正边界是所有 Wait 都返回

假设程序要连续处理多组独立任务,安全的复用形态是“上一轮完全收口,下一轮才建账”。官方文档的限制不是“看到计数为零就立即 Add”,而是新一轮的正向 Add 必须发生在所有上一轮 Wait 调用都返回之后。

func runRounds(rounds [][]func()) {
	var wg sync.WaitGroup

	for _, tasks := range rounds {
		// 当前轮从零开始计数;此处位于上一轮 Wait 返回之后。
		wg.Add(len(tasks))
		for _, task := range tasks {
			current := task
			go func() {
				defer wg.Done() // 当前任务结束后归还本轮计数。
				current()
			}()
		}
		wg.Wait() // 只有本轮收口后,循环才进入下一轮。
	}
}
WaitGroup 多轮任务中上一轮 Wait 返回与下一轮 Add 的边界说明图
图2:边界说明图,突出上一轮 Wait 返回与下一轮 Add 之间的复用条件;这不是执行时序截图。

如果有多个 goroutine 同时调用同一个 WaitGroup.Wait,也要把它们都视为上一轮的一部分:不能只等其中一个返回,就让下一轮从零开始 Add。需要跨轮并行时,应为不同的独立任务集合使用不同的同步边界,而不是让同一个计数器同时承担两轮生命周期。

三个容易误判的写法

  • 在 goroutine 内 Add:启动 goroutine 后才增加计数,可能与外层 Wait 竞争。应在创建 goroutine 前完成正向 Add。
  • 计数刚归零就复用:如果还有上一轮的 Wait 调用没有返回,不能开始下一轮。判断标准是所有 Wait 返回,而不是某个任务刚好结束。
  • 复制已经使用过的 WaitGroup:WaitGroup 首次使用后不能复制。把它作为值传递、放进会复制的结构体,都会让同步状态的归属变得不明确。

另外,Done 等价于 Add(-1),多调用一次会使计数变成负数并触发 panic。因此把 Done 放到 goroutine 开头、分支遗漏 Done,或在 defer 和显式代码中重复归还,都会破坏这一轮的计数闭环。

用一张表快速判断能否复用

检查点安全条件不满足时的风险
本轮 Add计数为零时,先 Add 再启动任务和 WaitWait 可能提前返回
本轮 Done每个任务恰好一次,通常用 defer 兜底永久等待或负数 panic
跨轮复用所有上一轮 Wait 都已返回两轮生命周期重叠,计数归属不清
对象传递首次使用后不复制 WaitGroup不同副本各自维护状态,等待结果失真

相关问题

WaitGroup 能不能在循环里重复使用?

可以,前提是每一轮都在上一轮所有 Wait 返回后才重新 Add,并且每轮任务都完整调用 Done。

为什么不能只看计数是否回到零?

因为复用条件还涉及正在进行的 Wait 调用。文档要求所有上一轮 Wait 返回,单看计数变化不足以表达完整的跨轮边界。

把 WaitGroup 当作“一轮任务的计数边界”而不是可随意重置的全局变量,复用问题就会简单很多:先建立当前轮,再等待当前轮收口,最后才允许下一轮开始。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
读写锁
下一篇
读写锁
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    982次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    986次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    1105次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    1075次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    916次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码