新闻中心

Go并发编程:sync.WaitGroup的正确使用与并发安全解析

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

Go并发编程:sync.WaitGroup的正确使用与并发安全解析

本文深入探讨go语言中`sync.waitgroup`的正确使用方法,特别是`wg.add()`调用时机的关键性。我们将通过示例代码分析,解释为何`wg.add()`必须在`go`语句之前执行,以及go内存模型如何确保这一顺序,从而有效避免竞态条件和潜在的程序崩溃,保障并发程序的稳定运行。

在Go语言的并发编程中,`sync.WaitGroup`是一个至关重要的同步原语,用于等待一组goroutine完成其任务。它通过一个内部计数器来工作:`Add(delta int)`方法增加计数器,`Done()`方法(等价于`Add(-1)`)减少计数器,而`Wait()`方法则会阻塞,直到计数器归零。

`sync.WaitGroup`的基本用法

以下是一个典型的`sync.WaitGroup`使用示例,它启动了多个goroutine并行执行任务,并在所有任务完成后才继续主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() // 任务完成后,减少计数器
}</p><p>func main() {
var wg sync.WaitGroup
wg.Add(4) // 预先告知WaitGroup将有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都调用了wg.Done()
fmt.Println("Done")

}

在这个例子中,`main`函数启动了四个`dosomething` goroutine。`wg.Add(4)`在所有`go`语句之前被调用,确保了`WaitGroup`的计数器在任何goroutine开始执行并可能调用`wg.Done()`之前就已经被正确设置。`wg.Wait()`则会等待这四个goroutine全部完成,即计数器归零,才会打印"Done"并退出。

`wg.Add()`调用时机的关键性

上述示例中,`wg.Add(4)`在所有`go`语句之前执行是至关重要的。虽然将`wg.Add(1)`分散到每个`go`语句之前也是正确的:

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")

}

但当已知goroutine数量时,一次性调用`wg.Add(N)`更为简洁高效。更重要的是,`wg.Add()`的调用必须在对应的`go`语句之前完成,以避免竞态条件和程序崩溃。

`WaitGroup`的内部计数器不能为负值。如果一个`wg.Done()`被调用时,计数器已经为零,程序将会发生`panic`。为了防止这种情况,我们必须确保每次`wg.Done()`被调用时,计数器都已经通过`wg.Add()`递增过。

家电小商城网站源码1.0 家电小商城网站源码1.0

家电公司网站源码是一个以米拓为核心进行开发的家电商城网站模板,程序采用metinfo5.3.9 UTF8进行编码,软件包含完整栏目与数据。安装方法:解压上传到空间,访问域名进行安装,安装好后,到后台-安全与效率-数据备份还原,恢复好数据后到设置-基本信息和外观-电脑把网站名称什么的改为自己的即可。默认后台账号:admin 密码:132456注意:如本地测试中127.0.0.1无法正常使用,请换成l

家电小商城网站源码1.0 0 查看详情 家电小商城网站源码1.0

Go内存模型与并发安全保障

Go语言的内存模型为我们提供了关于并发操作顺序的保证。理解这一点对于正确使用`sync.WaitGroup`至关重要:

  • 单个goroutine内的顺序: 在单个goroutine内部,所有语句的执行顺序看起来与它们在代码中出现的顺序一致。
  • `go`语句的发生顺序: Go内存模型明确指出,一个goroutine的执行不会在其对应的`go`语句完成之前开始。这意味着,在主goroutine中,`wg.Add()`语句的执行,保证在`go`语句启动的子goroutine开始执行其内部的`wg.Done()`之前。

因此,当我们在`go`语句之前调用`wg.Add()`时,Go的内存模型确保了以下顺序:

  1. 主goroutine执行`wg.Add(N)`,增加计数器。
  2. 主goroutine执行`go`语句,启动一个新的goroutine。
  3. 新的goroutine开始执行,并在完成任务后调用`wg.Done()`,减少计数器。

这种顺序保证了当子goroutine尝试调用`wg.Done()`时,`WaitGroup`的计数器已经被正确地初始化或递增,从而避免了计数器降至负数引发的`panic`。如果`wg.Add()`在`go`语句之后执行,就可能出现竞态条件:主goroutine可能在`wg.Add()`执行之前就启动了子goroutine,并且子goroutine可能在主goroutine执行`wg.Add()`之前就完成了任务并调用了`wg.Done()`,导致`panic`。

注意事项

  • `Add()`必须在`go`语句之前: 始终确保`wg.Add()`在启动任何可能调用`wg.Done()`的goroutine之前被调用。这是避免竞态条件和`panic`的核心规则。
  • `Done()`与`Add()`匹配: 每个`wg.Add(1)`或`wg.Add(N)`的增量都必须有对应的`wg.Done()`来抵消。通常,`wg.Done()`会放在`defer`语句中,以确保即使函数发生错误也能被调用,例如:
    func worker(id int, wg *sync.WaitGroup) {
        defer wg.Done() // 确保在函数退出时调用Done
        // ... 执行任务 ...
    }
  • 避免在被等待的goroutine中调用`Wait()`: 不要在被`WaitGroup`等待的goroutine内部调用同一个`WaitGroup`的`Wait()`方法,这会导致死锁。`Wait()`通常由主goroutine或协调goroutine调用。

总结

`sync.WaitGroup`是Go语言中实现并发协作的重要工具。正确理解和运用其`Add()`、`Done()`和`Wait()`方法,特别是`wg.Add()`的调用时机,是编写健壮、高效并发程序的关键。通过遵循Go内存模型的保证,确保`wg.Add()`在`go`语句之前执行,我们可以有效地避免竞态条件和运行时错误,从而构建出更加稳定可靠的并发应用。

以上就是Go并发编程:sync.WaitGroup的正确使用与并发安全解析的详细内容,更多请关注其它相关文章!


# 则会  # 沈阳论坛营销推广网站  # 上海制作seo优化费用  # 辽宁网络营销推广  # 小米手机网站建设总结  # 吉林省建设厅网站首页  # 佛山网站推广哪个好  # 优化网站关键词分析  # 增城区网站建设维护  # 湖北网站seo推广  # 藏装的网络营销推广方案  # 移除  # 启动了  # go  # 死锁  # 能在  # 并在  # 如何在  # 至关重要  # 前就  # 是一个  # 并发编程  # ai  # 工具  # go语言 


相关栏目: 【 科技资讯46185 】 【 网络学院92790


相关推荐: sublime如何优雅地处理行尾空格_sublime自动清理多余空白字符配置  Win11怎么隐藏桌面图标 Win11一键隐藏所有桌面元素及恢复显示  AI抖音网页版免费视频入口 AI抖音网页端最新视频实时观看  如何在低配置电脑上搭建轻量级J*a环境_占用更小的环境选择技巧  windows10怎么查看本机ip_windows10命令提示符ipconfig使用  Golang如何测试channel通信行为_Golang channel通信测试与分析方法  实现全屏滚动与导航点:专业教程  如何优雅地扩展SprykerGlue后端API授权逻辑,使用spryker/glue-backend-api-application-authorization-connector-extension  快手极速版在线观看 官方网页版登录地址  蛙漫安全无毒 官方认证的绿色入口  Win11文件资源管理器卡顿怎么修 Win11重置资源管理器进程优化响应速度【修复方法】  拼多多赚钱渠道_拼多多收益来源  win11如何卸载Windows更新补丁 Win11解决更新导致系统不稳定的问题【修复】  Win11怎么修改默认浏览器_Windows 11设置Chrome为默认  html怎么在cmd下运行php文件_cmd运行html中php文件方法【教程】  J*aScript map 迭代中检测空数组元素的有效方法  如何在 Excel Online 和 Google 表格中更改日期格式  “在文档元素之后找到了标记”是什么错误? 检查并修复XML中多个根元素的3个方法  蓝湖怎样用切图标注提对接效率_蓝湖用切图标注提对接效率【设计对接】  Win11 BitLocker密码忘了怎么办 Win11找回BitLocker恢复密钥方法【解决】  支付宝碰一碰设备是REDMI手机吗 博主拆机辟谣:处理器、内存都不一样  解决macOS上安装pyhdf时‘hdf.h’文件缺失的编译错误  优化MinIO list_objects_v2 操作的性能瓶颈与最佳实践  Win10系统服务哪些可以禁用 Win10安全优化服务列表【干货】  解决Tabulator日期时间排序问题的专业指南  神经网络二分类模型训练异常:高损失与完美验证准确率的排查与修正  C++如何实现异步操作_C++11使用std::future和std::async进行异步编程  Descript怎样用AI剪辑自动去噪_Descript用AI剪辑自动去噪【自动降噪】  处理动态列数据:J*a ArrayList的正确初始化与字符累加教程  快手网页版在线登录 快手网页版官网入口快速访问  sublime如何只显示或隐藏特定类型文件_sublime侧边栏文件过滤  poki网页游戏推荐_poki免费游戏平台入口  ExcelARRAYTOTEXT函数怎么自定义分隔符输出数组文本_ARRAYTOTEXT实现动态生成SQL语句  抖音未来赚钱的新趋势 2025年值得关注的变现风口分析  《噬血代码2》新预告片发布 展示游戏剧情  谷歌google账号注册详细步骤 谷歌账号注册官方教程  C++编译期如何执行复杂计算_C++模板元编程(TMP)技巧与应用  Lar*el Excel导入时生成自定义递增ID的策略与实践  Golang如何实现Web接口签名验证_Golang Web接口签名校验开发方法  J*a里如何使用N*igableMap进行导航操作_可导航Map操作技巧解析  深入理解与实现最大堆的Heapify过程:常见错误与修正  Go语言中高效处理x-www-form-urlencoded表单数据  支付宝解绑银行卡步骤_支付宝如何解除绑定银行卡  漫蛙manwa2最新登录网址_漫蛙manwa2手机网页版入口  微博网页版官方账号登录 微博网页版内容浏览使用指南  cad如何更改注释性对象的比例_cad注释性比例调整方法  C++指针和引用有什么区别_C++内存管理核心概念深度解析  Win11怎么设置鼠标主按键_Win11鼠标左右键功能互换  解决 Express.js 中 PUT 请求密码修改失败的路由配置指南  台积电1.4nm工艺A14瞄准2028:10年来性能提升80% 

搜索