sync.WaitGroup 使用误区

245 阅读1分钟

使用 Golang 的同学肯定对 WaitGroup 并不陌生,它的使用符合我们常见的思维方式并且使用也很简单,但是在简单之下也暗藏一下陷阱,今天就来看一下一个比较隐蔽的陷阱。

错误示例

package main

import (
	"fmt"
	"sync"
	"sync/atomic"
)

func main() {

	wg := sync.WaitGroup{}
	var v uint64

	for i := 0; i < 3; i++ {
		go func() {
			wg.Add(1)
			atomic.AddUint64(&v, 1)
			wg.Done()
		}()
	}

	wg.Wait()
	fmt.Println(v)
}

首先看一下上面这个示例,代码很简单开启协程去原子累加,但是它的结果会如我们所料的是3吗,答案不是,不信你可以去执行一下代码,也许是0也许是1,那么为什么会这样呢?问题的核心是我们执行 wg.Add 的时机不对,我们是在开启的协程里面来执行的,那么可能就会存在这样的情况,在 main groutine 执行 Wait 操作的时候,wg 的值已经是0了,所以整个的执行次数就不会和我们设想的一样

正确示例

package main

import (
	"fmt"
	"sync"
	"sync/atomic"
)

func main() {

	wg := sync.WaitGroup{}
	var v uint64

	for i := 0; i < 3; i++ {
		wg.Add(1)
		go func() {

			atomic.AddUint64(&v, 1)
			wg.Done()
		}()
	}

	wg.Wait()
	fmt.Println(v)
}

那么该怎样避免这种情况呢?就是我们的 Add 操作要在进入协程之前来进行就可以,所以可以在循环外层执行,也可以在循环内进入协程之前执行