waitGroup 使用方法
比如,我们要完成一个大的任务,需要使用并行的 goroutine 执行三个小任务,只有这三
个小任务都完成,我们才能去执行后面的任务。如果通过轮询的方式定时询问三个小任务
是否完成,会存在两个问题:一是,性能比较低,因为三个小任务可能早就完成了,却要
等很长时间才被轮询到;二是,会有很多无谓的轮询,空耗 CPU 资源。
那么,这个时候使用 WaitGroup 并发原语就比较有效了,它可以阻塞等待的
goroutine。等到三个小任务都完成了,再即时唤醒它们。
其实,很多操作系统和编程语言都提供了类似的并发原语。比如,Linux 中的 barrier、
Pthread(POSIX 线程)中的 barrier、C++ 中的 std::barrier、Java 中的 CyclicBarrier
和 CountDownLatch 等。由此可见,这个并发原语还是一个非常基础的并发类型。所
以,我们要认真掌握今天的内容,这样就可以举一反三,轻松应对其他场景下的需求了。
WaitGroup 的基本用法
Go 标准库中的 WaitGroup 提供了三个方法,保持了 Go 简洁的风格。
1
2
3
|
func (wg *WaitGroup) Add(delta int)
func (wg *WaitGroup) Done()
func (wg *WaitGroup) Wait()
|
Add,用来设置 WaitGroup 的计数值;
Done,用来将 WaitGroup 的计数值减 1,其实就是调用了 Add(-1);
Wait,调用这个方法的 goroutine 会一直阻塞,直到 WaitGroup 的计数值变为 0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
// 线程安全的计数器
type Counter struct {
mu sync.Mutex
count uint64
}
// 对计数值加一
func (c *Counter) Incr() {
c.mu.Lock()
c.count++
c.mu.Unlock()
}
// 获取当前的计数值
func (c *Counter) Count() uint64 {
c.mu.Lock()
defer c.mu.Unlock()
return c.count
}
// sleep 1秒,然后计数值加1
func worker(c *Counter, wg *sync.WaitGroup) {
defer wg.Done()
time.Sleep(time.Second)
c.Incr()
}
func main() {
var counter Counter
var wg sync.WaitGroup
wg.Add(10) // WaitGroup的值设置为10
for i := 0; i < 10; i++ { // 启动10个goroutine执行加1任务
go worker(&counter, &wg)
}
// 检查点,等待goroutine都完成任务
wg.Wait()
// 输出当前计数器的值
fmt.Println(counter.Count())
}
|