陈大剩博客

Golang 并发编程(七):Golang 中同步工具—WaitGroup

  • 陈大剩
  • 2025-02-26 21:48:43
  • 221

golang 标题

WaitGroup 介绍

sync.WaitGroup 类型是并发安全的,也是开箱即用的。例如,在声明 var wg sync.WaitGroup 就可以直接使用 wg 变量了。sync.WaitGroup 与前面讨论的几个同步工具一样,它一旦被真正使用就不能被复制了。

WaitGroup 类型拥有三个指针方法:AddDoneWait。你可以想象该类型中有一个计数器,它的默认值是 0。我们可以通过调用该类型值的 Add 方法来增加,或者减少这个计数器的值。具体例子如下:

var wg sync.WaitGroup
wg.Add(2)
go func() {
    defer wg.Done()
    fmt.Println("第一个协程")
}()
go func() {
    defer wg.Done()
    fmt.Println("第二个协程")
}()
wg.Wait()

这个例子中,一定要例子中的 2 个 goroutine 同时做完,才算是完成,先做好的就要等着其他未完成的,所有的 goroutine 要都全部完成才可以。

WaitGroup 生命周期

我们来玩一些有意思的事情,例子如下:

var wg sync.WaitGroup
wg.Add(2)
go func() {
    defer wg.Done()
    fmt.Println("第一个协程")
}()
wg.Wait()

首先猜猜这个代码会出现什么样结果?

第一个协程
fatal error: all goroutines are asleep - deadlock!

没错,会报错,为什么呢?这就得说一下,WaitGroup 的生命周期了。在 WaitGroup 一个技术周期中,总会是以 0 开始,然后再以 0 结束,当归为0才会唤醒 wg.Wait() ,否则 wg.Wait() 会一直阻塞,最终造成死锁(也就是上面的例子)。

接下来看看这个例子:

var wg sync.WaitGroup
wg.Add(1)
go func() {
    defer wg.Done()
    fmt.Println("第一个协程")
}()
wg.Wait() // 第一个计数周期结束

wg.Add(1)
go func() {
    defer wg.Done()
    fmt.Println("第二个协程")
}()
wg.Wait() // 第二个计数周期结束

输出如下:

第一个协程
第二个协程

这个例子中,WaitGroup 一共进行了两轮生命周期,生命周期总是以 Add(x) 开始,wg.Wait() 结束。如果一个此类值的 Wait 方法在它的某个计数周期中被调用,那么就会立即阻塞当前的 goroutine,直至这个计数周期完成。在这种情况下,该值的下一个计数周期,必须要等到这个 Wait 方法执行结束之后,才能够开始。

注意:如果在一个此类值的Wait方法被执行期间,跨越了两个计数周期,那么就会引发一个 panic。

sync.Once 只执行一次

sync 包中还有一个特殊的结构体类型 sync.Oncesync.Once 拥有一个 Do 方法。例子如下:

var one sync.Once
one.Do(func() {
    fmt.Println("只会执行一次")
})

对于同一个 sync.Once 类型的值的 Do 方法的有效调用次数永远会是 1。也就是说,无论用这个方法多少次,也无论在多次传递给它的参数值是否相等,都仅会调用一次,具体看下面这个例子:

var one sync.Once
var count int
for i := 0; i < 10; i++ {
    one.Do(func() {
       count++
    })
}
fmt.Println(count)

输出:

1

这个例子中,无论我们如何执行,这里输出都只会输出一次。

分享到:
0

说点儿什么吧

头像

表情

本站由陈大剩博客程序搭建 | 湘ICP备2023000975号| Copyright © 2017 - 陈大剩博客 | 本站采用创作共用版权:CC BY-NC 4.0

站长统计| 文章总数[126]| 评论总数[11]| 登录用户[26]| 时间点[130]

logo

登入

社交账号登录