
sync.waitgroup 是 go 语言中用于并发控制的重要工具,确保主 goroutine 等待所有子 goroutine 完成任务。本文深入探讨了 waitgroup 的正确使用方式,特别是 wg.add() 的放置时机,强调了其必须在 go 语句之前调用以有效避免竞态条件。我们将通过代码示例详细解析 add、done 和 wait 的协同工作机制,并解释 go 内存模型如何保证操作顺序,从而帮助开发者编写健壮的并发程序。
在 Go 语言的并发编程中,我们经常需要启动多个 goroutine 来并行执行任务。然而,主程序往往需要等待所有这些并发任务完成后才能继续执行或退出。sync.WaitGroup 就是 Go 标准库提供的一种轻量级且高效的同步原语,用于实现这种“等待所有任务完成”的机制。它允许一个 goroutine 等待一组其他 goroutine 完成它们的执行。
WaitGroup 的核心思想是维护一个内部计数器。当计数器归零时,Wait 方法就会解除阻塞。
sync.WaitGroup 主要由三个方法组成:
理解 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(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(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")
}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
}sync.WaitGroup 是 Go 语言中实现并发任务同步的基石。正确地理解和使用 wg.Add()、wg.Done() 和 wg.Wait() 是编写健壮、无竞态条件的并发程序的关键。核心原则是:wg.Add() 必须在启动相应 goroutine 的 go 语句之前执行,以确保 WaitGroup 的计数器在任何 Done() 操作之前都被正确初始化。遵循这些最佳实践将有助于您高效地管理 Go 中的并发任务。
以上就是掌握 Go 语言中的 sync.WaitGroup:并发任务的同步与管理的详细内容,更多请关注其它相关文章!
# 工具
# ai
# 并发编程
# 标准库
# 放在
# 是一个
# 前就
# go
# 茌平英文网站建设费用
# 湖南建设网站查询
# 网站建设学习头像插画
# 云南网站推广优化建设
# 海安市网站优化选哪家
# 产品seo 收录
# 淘宝十四天关键词排名
# 西昌专业网站建设
# 宁国谷歌seo公司
# 外贸seo博客外链案例
# 当你
# 主程序
# 如何在
# 多个
# 发生在
# 完成后
# 就会
相关文章:
抖音网页版企业服务中心登录入口_抖音网页版企业登录平台
Python多版本共存与虚拟环境管理深度指南
qq游戏跨平台入口_qq游戏多设备同步登录
Web Components中自定义开关组件状态同步的常见陷阱与解决方案
C++如何检测键盘输入_C++ _kbhit与_getch函数非阻塞输入
vivo手机参数配置怎么增强信号_vivo手机参数配置信号增强方法
漫蛙Manwa2官网入口地址分享 漫蛙漫画PC版永久访问通道
Lar*el 中按“Has One Of Many”关联模型排序的最佳实践
css滚动动画效果怎么实现_使用Animate.css滚动触发动画类
jQuery Mask 插件中实现电话号码固定前导零的教程
Archive of Our Own官网直达 AO3最新可用地址一览
零跑汽车11月交付量达70327台 实现连续9个月正增长
Pandas DataFrame 高效批量赋值:告别循环与笛卡尔积误区
天猫2025双十一0点秒杀攻略 天猫爆款抢购时间
微信网页版登录教程_微信网页版登录入口在哪
中兴Axon42Ultra怎样在文件App筛图_iPhone中兴Axon42Ultra文件App筛图【图片筛选】
Win10磁盘清理工具在哪 Win10打开并使用磁盘清理【教程】
抖音隐秘迷城小游戏入口_ 抖音冒险解谜小游戏秒玩
怎样把文件彻底粉碎无法恢复_Windows下安全删除敏感数据【隐私保护】
机器学习中对数变换预测结果的反向还原
汽水音乐网页版使用入口_汽水音乐电脑版播放指南
Win11怎么查看显卡显存 Win11显示适配器属性及专用视频内存查询
qq邮箱日历功能怎么用_创建日程与会议邀请的技巧
必由学在线入口 必由学网页版快速登录入口
深入理解J*a链表中的IPosition接口与使用
蛙漫安全无毒 官方认证的绿色入口
Go语言中JSON数据解析与字段访问教程
极兔快递快件信息查询系统 极兔快递官网运单号追踪
2025俄罗斯Yandex最新入口 官方网站地址及浏览器下载指南
Win11 BitLocker密码忘了怎么办 Win11找回BitLocker恢复密钥方法【解决】
腾讯QQ邮箱登录入口_QQ邮箱官方网站使用地址
AO3镜像入口大全 AO3网页版内容访问全集
c++ 获取系统当前时间 c++时间戳获取方法
Win11怎么开启省电模式_Win11电池节电模式自动开启
React/Next.js中实现列表项的动态移动与状态管理:兼论唯一键的重要性
修复二维数组索引越界异常:一维循环到二维坐标的正确映射
Golang如何使用context实现超时取消_Golang context超时取消模式实践
Shopware订单对象中获取产品自定义字段的正确方法
WooCommerce 购物车显示所有交叉销售商品教程
漫蛙网页登录入口 漫蛙漫画官方授权网址
正确连接J*aScript到HTML实现可点击图片与自定义事件处理
漫蛙MANWA漫画主页官方入口 漫蛙漫画最新在线阅读地址
mcjs网页版在线存档 mcjs云存档登录入口
PySpark中高效提取字符串右侧可变长度数字:使用regexp_extract
qq游戏网页版直接玩_qq游戏免下载快速入口
Django表单验证失败时保留用户输入数据的最佳实践
MAC怎么安装Homebrew包管理器_MAC为开发者和高级用户安装命令行工具
QQ邮箱网页版登录入口 QQ邮箱官方在线使用平台
优化大型XML文件解析:基于Python流式处理的内存高效方案
在WordPress中通过REST API获取BasicAuth保护的远程文章
*请认真填写需求信息,我们会在24小时内与您取得联系。