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


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 尚未完成。

堆友 堆友

Alibaba Design打造的设计师全成长周期服务平台,旨在成为设计师的好朋友

堆友 759 查看详情 堆友

Go 内存模型解析

为了理解为什么 `Add()` 必须在 `go` 语句之前,我们需要参考 Go 内存模型。Go 内存模型定义了并发程序中事件的顺序,并提供了关于内存操作可见性的保证。

  • **单 Goroutine 内的顺序**:Go 内存模型保证,在一个单独的 goroutine 内部,程序的执行顺序与代码的书写顺序一致。
  • **Goroutine 的启动**:一个 `go` 语句在它所启动的 goroutine 开始执行之前完成。这意味着,`go` 语句之前的任何操作,都保证在新的 goroutine 开始执行其代码之前完成。

结合这两点,当我们将 `wg.Add()` 放在 `go` 语句之前时,我们得到了以下保证:

  1. `wg.Add()` 操作在主 goroutine 中执行。
  2. `go` 语句在主 goroutine 中执行,并启动一个新的 goroutine。
  3. 根据 Go 内存模型,`wg.Add()` 保证在 `go` 语句之前完成。
  4. 新的 goroutine 在 `go` 语句完成之后才开始执行。
  5. 新 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  # SEO观察植物手绘  # 泰州网站建设和应用  # 安徽专注关键词排名  # 现在的seo什么情况  # 沧州孟村网站关键词排名  # SEO大牛谈创业女生  # 深圳网站优化加盟哪家好  # 展示了  # 就会  # go  # 完成任务  # 发生在  # 会在  # 则是  # 放在  # 器中  # 是一个  # 为什么  # 并发编程  # ai  # 工具 


相关栏目: 【 Google疑问12 】 【 Facebook疑问10 】 【 优化推广96088 】 【 技术知识133117 】 【 IDC资讯59369 】 【 网络运营7196 】 【 IT资讯61894


相关推荐: Symfony路由参数转换器:实体存在性验证与错误处理策略  红手指专业版app注册教程  mysql怎么导入sql文件_mysql导入sql文件的方法与技巧  《雷电模拟器》截图方法介绍  如何查找哪个composer包引入了特定的依赖?  Win10关闭UAC用户账户控制的方法 Win10降低安全提示等级【技巧】  Vue 3中独立响应式实例的创建与应用  PHP utf8_encode 字符编码转换陷阱与解决方案  PHP utf8_encode 字符编码转换疑难解析与最佳实践  阿里云共享相册入口在哪  《我的恋爱逃生攻略》中文名字输入方法  如何定制PrimeNG Sidebar的背景颜色  告别阻塞等待:如何使用GuzzlePromises优雅处理PHP异步操作,提升应用响应速度  Go语言反射机制:如何访问被嵌入结构体遮蔽的方法  《暗黑破坏神4》国服回归送狂欢礼包 价值6916元  中大网校app做题记录清除方法  《伊瑟》凶影追缉库卢鲁boss攻略  CSS动画如何实现图标旋转并放大_transform rotate scale @keyframes实现  J*aScript类型数组_TypedArray使用  FotoBalloon图片左右镜像教程  《tt语音》超级玩家开通方法  《华夏千秋》龙女试炼功法获取方法  如何在Podman容器中运行Composer_Docker替代品Podman的PHP与Composer容器化实践  嘴唇干裂起皮怎么办 唇部护理与预防干裂的方法【详解】  oppo手机如何通过下拉通知栏截图_oppo手机通知栏快捷截图方法  《爱笔思画x》魔棒工具抠图教程  QQ网页版入口导航 QQ网页版在线访问通道  wps文字怎么设置文字环绕图片的方式_wps文字如何设置文字环绕图片方式  Go Goroutine调度与并发执行深度解析  使用Selenium在无头Chrome中交互动态菜单和复选框的策略  PHP中获取HTTP响应状态消息:方法与限制  《淘宝联盟》推广自己的店铺方法  基于键值条件高效映射 Pandas DataFrame 多列数据  口腔诊所管理软件推荐  iphone16系列配置参数介绍  BunnyStream TUS视频上传指南:解决401认证错误与参数配置  画质怪兽120帧安卓和平精英免费版  《三国:谋定天下》平民全阶段通用阵容  《真我》申请退款方法  《合金装备4》有望推出重制版!制作人发话了  126邮箱网页在线登录2025_126邮箱网页版入口官方地址  吃完饭就犯困是什么原因 餐后嗜睡如何缓解  yandex网页版直接登录 yandex官方入口平台访问方法  Retrofit根路径POST请求:@POST("/") 的应用与解析  C#中的Record类型有什么优势?C# 9新特性Record与Class的用法区别  在PySimpleGUI中实现键盘按键绑定按钮事件  如何在Python中安全地将环境变量转换为整数并满足Mypy类型检查  解决Pandas DataFrame高度碎片化警告:高效创建多列的策略  《小宇宙》标记不友善评论方法  Bootstrap 5导航栏折叠功能失效:数据属性迁移指南 

 2025-11-02

了解您产品搜索量及市场趋势,制定营销计划

同行竞争及网站分析保障您的广告效果

点击免费数据支持

提交您的需求,1小时内享受我们的专业解答。

运城市盐湖区信雨科技有限公司


运城市盐湖区信雨科技有限公司

运城市盐湖区信雨科技有限公司是一家深耕海外推广领域十年的专业服务商,作为谷歌推广与Facebook广告全球合作伙伴,聚焦外贸企业出海痛点,以数字化营销为核心,提供一站式海外营销解决方案。公司凭借十年行业沉淀与平台官方资源加持,打破传统外贸获客壁垒,助力企业高效开拓全球市场,成为中小企业出海的可靠合作伙伴。

 8156699

 13765294890

 8156699@qq.com

Notice

We and selected third parties use cookies or similar technologies for technical purposes and, with your consent, for other purposes as specified in the cookie policy.
You can consent to the use of such technologies by closing this notice, by interacting with any link or button outside of this notice or by continuing to browse otherwise.