新闻中心
Go 并发编程:深入理解 sync.WaitGroup 的正确使用与并发安全

本文深入探讨 go 语言中 `sync.waitgroup` 的正确使用方法,强调 `wg.add()` 必须在 `go` 语句之前调用的重要性,以避免竞态条件和程序崩溃。通过结合 go 内存模型,详细解释了 `add()` 和 `done()` 调用的时序保证,并提供了示例代码和最佳实践,帮助开发者编写健壮的并发程序。
在 Go 语言的并发编程中,`sync.WaitGroup` 是一个非常重要的同步原语,它允许我们等待一组 goroutine 完成其执行。它通过一个内部计数器来工作:`Add()` 方法增加计数器,`Done()` 方法减少计数器,而 `Wait()` 方法会阻塞,直到计数器归零。
`sync.WaitGroup` 的基本用法
以下是一个常见的 `sync.WaitGroup` 使用示例,展示了如何等待多个后台 goroutine 完成任务:
package main
<p>import (
"fmt"
"sync"
"time"
)</p><p>func dosomething(millisecs time.Duration, wg <em>sync.WaitGroup) {
duration := millisecs </em> time.Millisecond
time.Sleep(duration)
fmt.Println("Function in background, duration:", duration)
wg.Done() // 任务完成后调用 Done()
}</p><p>func main() {
var wg sync.WaitGroup
wg.Add(4) // 预先增加计数器,表示将启动4个 goroutine
go dosomething(200, &wg)
go dosomething(400, &wg)
go dosomething(150, &wg)
go dosomething(600, &wg)</p><pre class="brush:php;toolbar:false;">wg.Wait() // 等待所有 goroutine 完成
fmt.Println("Done")}
上述代码的执行结果将是:
Function in background, duration: 150ms Function in background, duration: 200ms Function in background, duration: 400ms Function in background, duration: 600ms Done
这个示例清晰地展示了 `wg.Add(4)` 在启动 goroutine 之前被调用,以及每个 goroutine 在完成任务后调用 `wg.Done()`。这种模式是 `sync.WaitGroup` 的正确且推荐用法。
核心原则:`Add()` 的时机
使用 `sync.WaitGroup` 的一个关键原则是,wg.Add() 方法必须在对应的 go 语句之前被调用。这一点至关重要,它直接关系到程序的并发安全性和稳定性。
`WaitGroup` 的内部计数器从零开始。每次调用 `Add(delta int)`,计数器会增加 `delta`;每次调用 `Done()`,计数器会减少 1(相当于 `Add(-1)`)。当计数器降至零时,`Wait()` 方法才会解除阻塞。如果计数器在任何时候尝试降到零以下,`WaitGroup` 将会触发一个 panic。因此,确保 `Add()` 调用发生在 `Done()` 之前,是避免程序崩溃的关键。
并发安全与竞态条件
假设我们错误地将 `wg.Add()` 放在了 `go` 语句之后:
func main() {
var wg sync.WaitGroup
// 这是一个错误的示例,可能导致竞态条件甚至 panic
go dosomething(200, &wg)
wg.Add(1) // 如果 goroutine 启动并调用 Done() 发生在 Add() 之前,就会出问题
// ... 其他 goroutine
wg.Wait()
fmt.Println("Done")
}
在这种情况下,会发生竞态条件。Go 调度器在启动 `go dosomething(...)` 后,可能在 `wg.Add(1)` 被执行之前,就调度 `dosomething` goroutine 运行。如果 `dosomething` 足够快,它可能
会在 `wg.Add(1)` 执行之前调用 `wg.Done()`。此时,`WaitGroup` 的计数器仍然是 0,`Done()` 会尝试将其减为 -1,从而导致程序 panic。即使没有 panic,如果 `Wait()` 在 `Add()` 之前执行,也可能导致 `Wait()` 过早解除阻塞,而其他 goroutine 尚未完成。
Musho
AI网页设计Figma插件
76
查看详情
Go 内存模型解析
为了理解为什么 `Add()` 必须在 `go` 语句之前,我们需要参考 Go 内存模型。Go 内存模型定义了并发程序中事件的顺序,并提供了关于内存操作可见性的保证。
- **单 Goroutine 内的顺序**:Go 内存模型保证,在一个单独的 goroutine 内部,程序的执行顺序与代码的书写顺序一致。
- **Goroutine 的启动**:一个 `go` 语句在它所启动的 goroutine 开始执行之前完成。这意味着,`go` 语句之前的任何操作,都保证在新的 goroutine 开始执行其代码之前完成。
结合这两点,当我们将 `wg.Add()` 放在 `go` 语句之前时,我们得到了以下保证:
- `wg.Add()` 操作在主 goroutine 中执行。
- `go` 语句在主 goroutine 中执行,并启动一个新的 goroutine。
- 根据 Go 内存模型,`wg.Add()` 保证在 `go` 语句之前完成。
- 新的 goroutine 在 `go` 语句完成之后才开始执行。
- 新 goroutine 中的 `wg.Done()` 操作会在其内部逻辑执行完毕后调用。
因此,`wg.Add()` 保证在 `wg.Done()` 之前发生,从而避免了竞态条件和计数器下溢的风险。
`Add()` 的灵活调用方式
虽然一次性调用 `wg.Add(N)` 是高效且推荐的方式,尤其当已知 goroutine 数量时,但也可以选择在每次启动 goroutine 之前调用 `wg.Add(1)`。例如:
func main() {
var wg sync.WaitGroup
wg.Add(1)
go dosomething(200, &wg)
wg.Add(1)
go dosomething(400, &wg)
wg.Add(1)
go dosomething(150, &wg)
wg.Add(1)
go dosomething(600, &wg)
<pre class="brush:php;toolbar:false;">wg.Wait()
fmt.Println("Done")}
这种写法在功能上是正确的,因为它同样保证了每个 `Add(1)` 都发生在对应的 `go` 语句之前。然而,当 goroutine 数量已知时,一次性调用 `wg.Add(N)` 更加简洁和高效。如果 goroutine 的数量是动态的,那么在每次启动 goroutine 之前调用 `wg.Add(1)` 则是更合适的选择。
注意事项与最佳实践
- **`Add()` 必须在 `go` 语句之前**:这是最核心的规则,务必遵守以确保并发安全。
- **`WaitGroup` 传递指针**:在将 `WaitGroup` 传递给 goroutine 时,必须传递其指针 (`*sync.WaitGroup`),而不是值。否则,每个 goroutine 将操作 `WaitGroup` 的副本,导致同步失败。
- **每个 `Done()` 对应一个 `Add()`**:确保 `Done()` 的调用次数与 `Add()` 增加的计数器值相匹配,否则 `Wait()` 可能永远阻塞或导致 panic。
- **错误处理**:在实际应用中,goroutine 内部的错误处理也很重要。`WaitGroup` 仅负责同步 goroutine 的完成,不负责错误传递。可以结合 `context` 或通道来处理错误。
总结
`sync.WaitGroup` 是 Go 语言中实现并发同步的强大工具。正确理解和使用 `wg.Add()` 的时机是编写健壮、无竞态条件并发程序的关键。通过始终确保 `wg.Add()` 在 `go` 语句之前执行,并结合对 Go 内存模型的理解,开发者可以有效地协调 goroutine 的执行,从而构建高性能且可靠的 Go 应用程序。
以上就是Go 并发编程:深入理解 sync.WaitGroup 的正确使用与并发安全的详细内容,更多请关注其它相关文章!
# 移除
# 郑州营销推广诚信企业
# seo独家揭秘
# 厦门seo新闻
# 高定营销推广
# 免费茶叶网站建设
# 怎样上推特趋势网站推广
# 购物网站内部优化
# seo批量发广告
# 咸阳快速网站建设流程
# 网站最有效推广的方式是
# 就会
# 这是
# go
# 完成任务
# 发生在
# 会在
# 则是
# 如何在
# 放在
# 是一个
# 为什么
# 并发编程
# ai
# 工具
相关栏目:
【
科技资讯46185 】
【
网络学院92790 】
相关推荐:
浏览器打开即用 美图秀秀网页版入口
R星幕后开发视频泄露 包含《GTA6》等多款大作
GemBox Document HTML转PDF垂直文本渲染问题及解决方案
SteamMachine定价或为699美元 大家想入手吗?
印象笔记怎样用批量导出备知识库_印象笔记用批量导出备知识库【备份方法】
如何高效处理PHP中的Excel数据导入导出?PortPHP/Spreadsheet助你轻松搞定!
在python-socketio事件处理器中安全访问Flask应用上下文
J*a应用程序首次运行自动创建文件与目录的最佳实践
Golang指针如何与map组合使用_Golang map指针组合实践
多闪网页版在线观看免费入口_多闪官网访问入口
Django表单提交验证失败后保持字段值不刷新
Golang如何使用buffered channel提高性能_Golang buffered channel优化技巧
抖音从哪里进入网页版_抖音官方入口链接
Python自定义类排序:解决lambda键值访问TypeError的实践指南
Go语言中对Map值调用带指针接收者方法:原理与最佳实践
小红书怎么解除第三方平台绑定_小红书多平台登录解绑方法介绍
一加Ace 6T支持全新明眸护眼:通过了最严苛的护眼小金标认证
HTML长属性值处理:表单action路径优化与代码规范应对
Spring Boot嵌入式服务器与J*a EE:功能支持深度解析
谷歌浏览器如何快速清除某个网站的数据_Chrome网站缓存清理方法
TikTok搜索不到用户发布内容怎么办 TikTok用户内容搜索优化方法
C++如何实现单例模式_C++设计模式之线程安全的单例写法
J*aScript中向JSON对象添加新属性的正确姿势
电脑屏幕颜色不舒服怎么办_Windows夜间模式与色彩校准教程【护眼技巧】
优化Log4j2控制台输出性能:解决异步日志瓶颈
2306选座时如何选靠窗位置_12306选座靠窗座位查看方法解析
解决macOS上安装pyhdf时‘hdf.h’文件缺失的编译错误
如何仅使用CSS更改登录界面背景图像图标的颜色
怎么在html里运行vbs脚本_html中运行vbs脚本方法【教程】
Golang如何优雅处理error_Golang error处理最佳实践总结
J*aScript中正确使用querySelectorAll与复杂CSS选择器
QQ官网正版登录链接 QQ在线登录入口最新
uc浏览器网页版入口 uc浏览器网页版最新网址
192.168.1.1管理中心入口 192.168.1.1路由器网页设置平台
优化MinIO list_objects_v2 操作的性能瓶颈与最佳实践
移动端XML文件怎么转换成Excel 手机和平板上的解决方案
汽水音乐网页版使用入口_汽水音乐电脑版播放指南
J*a如何使用AtomicInteger控制计数_J*a无锁计数器性能分析
谷歌推RCS信息存档功能:公司可监控员工私密信息!
如何使用 Excel 发布器与 Power BI 分享 Excel 洞察
Go语言中JSON数据解码与字段访问指南
win11 arm版怎么安装 M1/M2 Mac虚拟机安装ARM win11的方法
深入理解J*a编译器的兼容性选项:从-source到--release
Django模型中自动计算可用余额的实现方法
Lar*el如何生成PDF或Excel文件_Lar*el文档导出工具与使用教程
外媒分析《GTA6》定价:卖100美元可以但真没必要!
解决移动端滚动问题的overflow属性应用指南
outlook中文官网入口地址 outlook官方中文版直达首页链接
知音漫客官网漫画下载_知音漫客网页版阅读记录
包子漫画官方网站在线链接-包子漫画在线阅读平台主页地址


2025-11-02
浏览次数:次
返回列表