新闻中心
掌握 Go 语言中的 sync.WaitGroup:并发任务的同步与管理

sync.waitgroup 是 go 语言中用于并发控制的重要工具,确保主 goroutine 等待所有子 goroutine 完成任务。本文深入探讨了 waitgroup 的正确使用方式,特别是 wg.add() 的放置时机,强调了其必须在 go 语句之前调用以有效避免竞态条件。我们将通过代码示例详细解析 add、done 和 wait 的协同工作机制,并解释 go 内存模型如何保证操作顺序,从而帮助开发者编写健壮的并发程序。
引言:理解 Go 并发中的 sync.WaitGroup
在 Go 语言的并发编程中,我们经常需要启动多个 goroutine 来并行执行任务。然而,主程序往往需要等待所有这些并发任务完成后才能继续执行或退出。sync.WaitGroup 就是 Go 标准库提供的一种轻量级且高效的同步原语,用于实现这种“等待所有任务完成”的机制。它允许一个 goroutine 等待一组其他 goroutine 完成它们的执行。
WaitGroup 的核心思想是维护一个内部计数器。当计数器归零时,Wait 方法就会解除阻塞。
sync.WaitGroup 的核心组件
sync.WaitGroup 主要由三个方法组成:
- Add(delta int): 用于增加或减少 WaitGroup 的计数器。通常,delta 是正数,表示要等待的 goroutine 数量。例如,wg.Add(1) 表示增加一个需要等待的 goroutine。
- Done(): 相当于 Add(-1)。当一个 goroutine 完成其任务时,它应该调用 wg.Done() 来减少 WaitGroup 的计数器。
- Wait(): 阻塞调用它的 goroutine,直到 WaitGroup 的计数器变为零。这意味着所有通过 Add 方法添加的 goroutine 都已调用 Done() 完成任务。
正确使用 wg.Add() 的时机
理解 wg.Add() 的放置时机对于避免并发中的竞态条件至关重要。
示例代码:标准且正确的用法
以下是一个典型的 sync.WaitGroup 使用示例,它展示了如何正确地初始化 WaitGroup 并等待多个 goroutine 完成:
package main
import (
"fmt"
"sync"
"time"
)
// dosomething 模拟一个耗时操作,并在完成后调用 wg.Done()
func dosomething(millisecs time.Duration, wg *sync.WaitGroup) {
duration := millisecs * time.Millisecond
time.Sleep(duration) // 模拟工作负载
fmt.Println("Function in background, duration:", duration)
wg.Done() // 任务完成后,通知 WaitGroup
}
func main() {
var wg sync.WaitGroup // 声明一个 WaitGroup 变量
// 在所有 go 语句之前,一次性设置需要等待的 goroutine 数量
wg.Add(4)
// 启动四个 goroutine
go dosomething(200, &wg)
go dosomething(400, &wg)
go dosomething(150, &wg)
go dosomething(600, &wg)
wg.Wait() // 阻塞主 goroutine,直到所有子 goroutine 完成
fmt.Println("Done") // 所有任务完成后,打印 "Done"
}输出结果 (顺序可能不同,但最终都会打印 "Done"):
Musho
AI网页设计Figma插件
76
查看详情
Function in background, duration: 150ms Function in background, duration: 200ms Function in background, duration: 400ms Function in background, duration: 600ms Done
为何 wg.Add() 必须在 go 语句之前?
上述示例中,wg.Add(4) 发生在所有 go dosomething(...) 语句之前。这是 WaitGroup 正确工作的关键。如果 wg.Add() 发生在 go 语句之后,可能会引入竞态条件,导致程序行为不确定甚至崩溃。
竞态条件 (Race Condition) 的风险: 如果将 wg.Add(N) 放在 go 语句之后,或者更糟糕地,将 wg.Add(1) 放在被启动的 goroutine 内部,那么主 goroutine 有可能在子 goroutine 启动并调用 wg.Add() 之前就执行到 wg.Wait()。在这种情况下,WaitGroup 的计数器可能还未增加,wg.Wait() 会立即返回(因为计数器为零),而子 goroutine 仍在后台运行。这导致主程序过早结束,无法等待所有任务完成。
WaitGroup 计数器低于零的恐慌 (Panic):WaitGroup 的计数器不能降到零以下。如果一个 goroutine 在 wg.Add() 增加计数器之前就调用了 wg.Done(),那么计数器会从零变为负数,这将导致程序发生 panic。例如,如果 wg.Add(1) 被放在 dosomething 函数内部,且该 goroutine 启动速度非常快,在主 goroutine 执行到 wg.Add(1) 之前就完成了任务并调用了 wg.Done(),就会出现这种情况。
Go 内存模型的保证: Go 语言的内存模型提供了一些关于事件顺序的保证。其中一个重要的保证是:go 语句的执行(即启动一个新的 goroutine)发生在被启动的 goroutine 实际开始运行之前。这意味着,如果在 go 语句之前调用 wg.Add(),那么 wg.Add() 的操作一定会在新的 goroutine 开始执行其代码(包括 wg.Done())之前完成。这种顺序保证消除了竞态条件,确保 WaitGroup 的计数器在任何 Done() 操作发生之前都已正确增加。
wg.Add() 的灵活调用方式
虽然一次性调用 wg.Add(N) 是最常见且推荐的做法(当你知道需要等待的 goroutine 数量时),但在某些场景下,你也可以在每次启动一个 goroutine 前调用 wg.Add(1)。
func main() {
var wg sync.WaitGroup
// 每次启动一个 goroutine 前,增加计数器
wg.Add(1)
go dosomething(200, &wg)
wg.Add(1)
go dosomething(400, &wg)
wg.Add(1)
g
o dosomething(150, &wg)
wg.Add(1)
go dosomething(600, &wg)
wg.Wait()
fmt.Println("Done")
}这种逐个添加的方式在功能上是正确的,因为它同样保证了 wg.Add(1) 发生在对应的 go 语句之前。然而,当你知道确切的 goroutine 数量时,一次性调用 wg.Add(N) 更加简洁和高效。逐个添加的方式在循环中启动 goroutine 时可能更有用,例如:
func main() {
var wg sync.WaitGroup
durations := []time.Duration{200, 400, 150, 600}
for _, d := range durations {
wg.Add(1) // 每次迭代增加一个计数
go dosomething(d, &wg)
}
wg.Wait()
fmt.Println("Done")
}注意事项与最佳实践
- 指针传递 WaitGroup: 始终将 sync.WaitGroup 作为指针 (*sync.WaitGroup) 传递给函数,因为 WaitGroup 是一个值类型,如果按值传递,每个 goroutine 将获得 WaitGroup 的副本,导致同步失败。
-
确保 Done() 被调用: 确保每个通过 Add() 增加计数器的 goroutine 最终都会调用 Done()。通常,这会在函数的 defer 语句中完成,以保证即使函数提前返回或发生错误,Done() 也能被调用。
func dosomethingSafe(millisecs time.Duration, wg *sync.WaitGroup) { defer wg.Done() // 确保在函数退出时调用 Done() duration := millisecs * time.Millisecond time.Sleep(duration) fmt.Println("Function in background, duration:", duration) // 假设这里可能会有panic或者提前return } - 避免计数器归零以下: 如前所述,确保 Add() 总是先于 Done() 执行,以防止计数器变为负数导致 panic。
总结
sync.WaitGroup 是 Go 语言中实现并发任务同步的基石。正确地理解和使用 wg.Add()、wg.Done() 和 wg.Wait() 是编写健壮、无竞态条件的并发程序的关键。核心原则是:wg.Add() 必须在启动相应 goroutine 的 go 语句之前执行,以确保 WaitGroup 的计数器在任何 Done() 操作之前都被正确初始化。遵循这些最佳实践将有助于您高效地管理 Go 中的并发任务。
以上就是掌握 Go 语言中的 sync.WaitGroup:并发任务的同步与管理的详细内容,更多请关注其它相关文章!
# 工具
# go
# 就会
# 前就
# 是一个
# 放在
# 标准库
# 并发编程
# ai
# 茌平英文网站建设费用
# 湖南建设网站查询
# 网站建设学习头像插画
# 云南网站推广优化建设
# 海安市网站优化选哪家
# 产品seo 收录
# 淘宝十四天关键词排名
# 西昌专业网站建设
# 宁国谷歌seo公司
# 外贸seo博客外链案例
# 当你
# 主程序
# 如何在
# 多个
# 发生在
# 完成后
相关栏目:
【
科技资讯46185 】
【
网络学院92790 】
相关推荐:
照顾宝贝2小游戏免费秒玩入口
J*a里如何使用forEach遍历Map_Map遍历方法说明
PySpark中从现有列右侧提取可变长度字符创建新列的教程
谷歌浏览器一键优化方案_谷歌浏览器直达主页极速不卡版
哔哩哔哩忘记密码了怎么找回_哔哩哔哩密码找回方法
C++如何操作注册表_Windows平台下C++读写注册表的API函数详解
微博网页版怎么开启两步验证_微博网页版账号安全两步验证设置方法
Python中如何避免重复条件判断:利用数据结构实现动态逻辑
我的世界官方游戏入口 我的世界官网平台直达链接
Win11怎么隐藏桌面图标 Win11一键隐藏所有桌面元素及恢复显示
4399网页游戏电脑版全新入口 4399电脑端在线玩指南
文心一言怎样用批量生成做多版文案_文心一言用批量生成做多版文案【批量创作】
优化MinIO list_objects_v2 操作的性能瓶颈与最佳实践
Angular Material 垂直步进器:实现底部到顶部排序的教程
实现分段式页面滚动导航:CSS与J*aScript教程
Android Studio计算器C键逻辑错误排查与修复:条件判断优化指南
mysql备份恢复性能优化_mysql备份恢复性能优化方法
12306选座怎么选到商务座_12306商务座选择与配置说明
Windows10怎么开启夜间模式 Windows10系统设置调整色温与亮度缓解夜间用眼疲劳【教程】
2306选座时如何选靠窗位置_12306选座靠窗座位查看方法解析
yy漫画网页版官方入口_yy漫画官网登录页面链接
C++如何打印当前代码行号与文件名_C++预定义宏FILE与LINE的使用
Go语言中高效处理x-www-form-urlencoded表单数据
Sublime Text怎么设置垂直标尺_Sublime配置Rulers规范代码长度
支付宝如何管理隐私设置_支付宝隐私保护的配置技巧
在J*a里如何理解依赖关系的方向_依赖方向在模块结构中的作用
J*aScript数组对象转换:按指定键分组与值收集
魅族20怎样在浏览器开无图省流_iPhone魅族20浏览器开无图省流【流量节省】
192.168.1.1管理中心入口 192.168.1.1路由器网页设置平台
漫蛙manwa2最新登录网址_漫蛙manwa2手机网页版入口
QQ邮箱官方邮箱登录入口 QQ邮箱网页版快速访问
QQ邮箱官方网站登录入口_QQ邮箱网页版在线使用
如何使用Node.js csv 包按条件移除含空字段的CSV记录
age动漫网站入口 age动漫官网直接访问入口
Descript怎样用AI剪辑自动去噪_Descript用AI剪辑自动去噪【自动降噪】
J*aScript生成器_j*ascript异步迭代
Surface怎么安装系统 微软Surface Pro U盘重装win11教程
海量存储:机器视觉智能化的核心基石
在Go语言中利用后缀数组处理多字符串:实现高效文本匹配与自动补全
汽水音乐车机版8.9下载 汽水音乐车机版8.9版本安装入口
谷歌google账号注册详细步骤 谷歌账号注册官方教程
高德地图沿途添加点失败如何解决 高德多点规划方法
c++如何实现一个简单的ECS框架_c++数据驱动设计与游戏开发
J*aScript设计模式实践_j*ascript代码优化
TikTok国际版官网直达_TikTok国际版官网直达进入在线观看
夸克AO3官网入口_AO3镜像网站2025推荐
Win11怎么合并任务栏图标 Win11开启任务栏合并减少图标占空间【方法】
腾讯视频怎么举报不良内容_腾讯视频内容举报流程与违规信息处理方法
在J*a中如何使用BigDecimal进行高精度计算_BigDecimal类应用指南
邮编格式怎么匹配地址_根据邮编格式快速匹配详细地址的技巧


2025-11-02
浏览次数:次
返回列表
o dosomething(150, &wg)
wg.Add(1)
go dosomething(600, &wg)
wg.Wait()
fmt.Println("Done")
}