0%

【Golang】Usage of Sync.WaitGroup

了解+整理sync.WaitGroup的用法,并记录一道随机面试题目(第一次见到golang相关的面试题.

Motivation

当在go程序中实现多线程时,经常需要通过time.sleep的方式使主程序等待其他线程完成。但利用sleep使主程序idle资源开销较大,并且无法判断需要等待的有效时长.
此处可以通过使用channel的方式来使主程序等待所有其他线程完成任务.

1
2
3
4
5
6
7
8
9
10
11
12
func main() {
c := make(chan bool )
for i := ; i < ; i++ {
go func(i int) {
fmt.print(i)
c <- true
}(i)
}
for i:=; i <; i++ {
<-c //release bool in channel
}
}

使用channel可以完美解决等待时长随机的问题,但内存开销较大,有大炮打蚊子的嫌疑(。

sync.WaitGroup则能更加方便高效地帮助我们实现这个需求。

Intro

WaitGroup 内置counter从0开始,包含三个方法Add(), Done(),Wait() 用来更新和控制计数器的数量。

  • Add(n):将计数器设置为n,注意n不能是负数
  • Done():对计数器进行-1操作,相当于Add(-1);由于n不能为负,注意Done的调用次数也不可以超过n的总数
  • Wait():在计数器没有归0前将会阻塞代码的运行

使用sync.WaitGroup可以将代码修改成这样:

1
2
3
4
5
6
7
8
9
10
11
func main() {
wg := sync.WaitGroup{}
wg.Add(n) // 设为循环的总计数
for i := ; i < ; i++ {
go func(i int) {
fmt.print(i)
wg.Done() // 每次完成-1
}(i)
}
wg.Wait() //主程序在计数器归零后才能结束
}

Notice: WaitGroup对象不可引用,需要在函数传入值时使用address:

1
2
3
4
5
6
7
8
9
10
11
12
13
unc main() {
wg := sync.WaitGroup{}
wg.Add(n) // 设为循环的总计数
for i := ; i < ; i++ {
go counter(i, &wg)
}
wg.Wait() //主程序在计数器归零后才能结束
}

func counter(i int, wg * sync.WaitGroup) {
fmt.print(i)
wg.Done()
}

Interview Question

如何利用两个go routine交替打印1-100的奇偶数?(输出时需按照从小到大顺序输出)

  • 利用sync.WaitGroup首先使主程序保持执行状态
  • 在两个go routine中实现输出后对计数器执行减1操作,因此可以实现互相堵塞交替输出
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
package main

import (
"fmt"
"sync"
)

func main() {
var wg sync.WaitGroup

for i := 1; i <= 100; i++ {
if i%2 == 0 {
wg.Add(1)
go func(i int) {
defer wg.Done()
fmt.Println("Routine 1 printing even:", i)
}(i)
wg.Wait()
} else {
wg.Add(1)
go func(i int) {
defer wg.Done()
fmt.Println("Routine 2 printing odd:", i)
}(i)
wg.Wait()
}
}
}