Go语言同步原语与数据竞争:WaitGroup

简介: 本文介绍了 Go 语言中 `sync.WaitGroup` 的使用方法和注意事项。作为同步原语,它通过计数器机制帮助等待多个 goroutine 完成任务。核心方法包括 `Add()`(设置等待数量)、`Done()`(减少计数)和 `Wait()`(阻塞直到计数归零)。文章详细讲解了其基本原理、典型用法(如等待 10 个 goroutine 执行完毕),并提供了代码示例。同时指出常见错误,例如 `Add()` 必须在 goroutine 启动前调用,以及 WaitGroup 不可重复使用。最后总结了适用场景和使用要点,强调避免竞态条件与变量捕获陷阱。

 

在Go语言并发编程中,我们经常需要等待多个 goroutine 执行完毕后再继续下一步操作。Go 提供的 sync.WaitGroup 就是专为这种**“等待一组任务完成”**而设计的同步原语。


一、基本原理

sync.WaitGroup 提供三个主要方法:

方法 说明
Add(n int) 设置等待的 goroutine 数量(加计数)
Done() 每个 goroutine 完成时调用(减计数)
Wait() 阻塞主 goroutine,直到计数归零

它内部使用计数器+条件变量,当所有 goroutine 都调用 Done() 后,Wait() 才会解除阻塞。


二、典型用法

示例:等待 10 个 goroutine 执行完毕

package main
import (
    "fmt"
    "sync"
)
func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done() // 每个 goroutine 完成时调用
    fmt.Printf("Worker %d is working...\n", id)
    // 模拟工作
}
func main() {
    var wg sync.WaitGroup
    for i := 1; i <= 10; i++ {
        wg.Add(1) // 每启动一个 goroutine,加1
        go worker(i, &wg)
    }
    wg.Wait() // 阻塞直到所有 goroutine 完成
    fmt.Println("All workers done.")
}

输出示意:

Worker 1 is working...
Worker 2 is working...
...
All workers done.

三、常见错误及注意事项

1. Add() 必须在 goroutine 启动前调用

错误示例:

go func() {
    wg.Add(1) // 此时 goroutine 可能已开始执行,竞态风险
    ...
}()

正确做法:

wg.Add(1)
go func() {
    ...
    wg.Done()
}()

2. 不可重复使用已完成的 WaitGroup(没有“重置”功能)

WaitGroup 设计为一次性同步器,不建议重复使用,若确需控制并发次数,可用 sync.Poolsemaphore 替代。


四、结合匿名函数使用

for i := 0; i < 5; i++ {
    wg.Add(1)
    go func(i int) {
        defer wg.Done()
        fmt.Println("i:", i)
    }(i)
}
wg.Wait()

⚠️ 注意:传参 i 必须显式传入闭包,避免捕获变量陷阱。


五、使用场景

  • • 等待一组任务执行完成(如:并发下载、批量计算)
  • • 控制主函数在 goroutine 完成后再退出
  • • 可搭配 Channel 和 Context 使用,实现更复杂的并发控制模型

六、小结

  • sync.WaitGroup 是 Go 并发编程中最常用的同步工具之一。
  • • 使用 Add/Done/Wait 实现多协程间的同步等待。
  • • 使用时避免竞态和变量捕获问题。

 

相关文章
|
2月前
|
人工智能 安全 Shell
Go并发编程避坑指南:从数据竞争到同步原语的解决方案
在高并发场景下,如钱包转账,数据一致性至关重要。本文通过实例演示了 Go 中如何利用 `sync.Mutex` 和 `sync.RWMutex` 解决数据竞争问题,帮助开发者掌握并发编程中的关键技能。
Go并发编程避坑指南:从数据竞争到同步原语的解决方案
|
2月前
|
存储 监控 算法
企业电脑监控系统中基于 Go 语言的跳表结构设备数据索引算法研究
本文介绍基于Go语言的跳表算法在企业电脑监控系统中的应用,通过多层索引结构将数据查询、插入、删除操作优化至O(log n),显著提升海量设备数据管理效率,解决传统链表查询延迟问题,实现高效设备状态定位与异常筛选。
115 3
|
5月前
|
Go
Go语言同步原语与数据竞争:Mutex 与 RWMutex
在Go语言并发编程中,数据竞争是多个goroutine同时读写共享变量且未加控制导致的问题,可能引发程序崩溃或非确定性错误。为解决此问题,Go提供了`sync.Mutex`和`sync.RWMutex`两种同步机制。`Mutex`用于保护临界区,确保同一时间只有一个goroutine访问;`RWMutex`支持多读单写的细粒度控制,适合读多写少场景。使用时需避免死锁,并借助`-race`工具检测潜在的数据竞争,从而提升程序稳定性和性能。
178 51
|
5月前
|
编译器 测试技术 Go
Go语言同步原语与数据竞争:数据竞争的检测工具
本文介绍了 Go 语言中数据竞争(Data Race)的概念及其检测方法。数据竞争发生在多个 Goroutine 无同步访问共享变量且至少一个为写操作时,可能导致程序行为不稳定或偶发崩溃。Go 提供了内置的竞态检测器(Race Detector),通过 `-race` 参数可轻松检测潜在问题。文章还展示了如何使用锁或原子操作修复数据竞争,并总结了在开发和 CI 流程中启用 `-race` 的最佳实践,以提升程序稳定性和可靠性。
|
5月前
|
安全 Go 调度
Go同步原语与数据竞争:原子操作(atomic)
本文介绍了Go语言中`sync/atomic`包的使用,帮助避免多goroutine并发操作时的数据竞争问题。原子操作是一种不可中断的操作,确保变量读写的安全性。文章详细说明了常用函数如`Load`、`Store`、`Add`和`CompareAndSwap`的功能与应用场景,并通过并发计数器示例展示了其实现方式。此外,对比了原子操作与锁的优缺点,强调原子操作适用于简单变量的高效同步,而不适合复杂数据结构。最后提醒开发者注意使用场景限制,合理选择同步工具以优化性能。
|
9月前
|
测试技术 Go API
Go 切片导致 rand.Shuffle 产生重复数据的原因与解决方案
在 Go 语言开发中,使用切片时由于其底层数据共享特性,可能会引发意想不到的 Bug。本文分析了 `rand.Shuffle` 后切片数据重复的问题,指出原因在于切片是引用类型,直接赋值会导致底层数组共享,进而影响原始数据。解决方案是使用 `append` 进行数据拷贝,确保独立副本,避免 `rand.Shuffle` 影响原始数据。总结强调了切片作为引用类型的特性及正确处理方法,确保代码稳定性和正确性。
247 82
|
数据采集 网络协议 测试技术
使用Go Validator在Go应用中有效验证数据
使用Go Validator在Go应用中有效验证数据
|
JSON 测试技术 Go
零值在go语言和初始化数据
【7月更文挑战第10天】本文介绍在Go语言中如何初始化数据,未初始化的变量会有对应的零值:bool为`false`,int为`0`,byte和string为空,pointer、function、interface及channel为`nil`,slice和map也为`nil`。。本文档作为指南,帮助理解Go的数据结构和正确使用它们。
222 22
零值在go语言和初始化数据
|
存储 算法 Java
Go 通过 Map/Filter/ForEach 等流式 API 高效处理数据
Go 通过 Map/Filter/ForEach 等流式 API 高效处理数据
|
数据采集 缓存 IDE
Go中遇到http code 206和302的获取数据的解决方案
文章提供了解决Go语言中处理HTTP状态码206(部分内容)和302(重定向)的方案,包括如何获取部分数据和真实请求地址的方法,以便程序员能快速完成工作,享受七夕时光。
965 0
Go中遇到http code 206和302的获取数据的解决方案