sync的waitgroup功能
WaitGroup
使用多线程时,进行等待多线程执行完毕后,才可以结束函数,有两个选择
channel
waitgroup
首先使用channel
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
func add (n *int , isok chan bool){
for i :=0 ;i <1000 ; i ++ {
*n = *n + 1
}
isok <- true
}
func main () {
var ok = make(chan bool , 2)
var i,u = 0,0
go add(&i , ok)
go add(&i , ok)
for <- ok {
u++
if u == 2 {
break
}
}
fmt.Println(i)
}
|
但是,如果线程一旦增多,就会导致代码冗余,增加负担,可以使用sync.WaitGroup包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
func add (n *int , wait *sync.WaitGroup) {
for i := 0 ; i <1000 ; i ++ {
*n = *n + 1
}
defer wait.Done()
}
func main () {
var wait sync.WaitGroup
var i = 0
wait.Add(2)
go add(&i , &wait)
go add(&i , &wait)
wait.Wait()
fmt.Println(i)
}
|
仿sync.WaitGroup功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
type Wait struct {//创建一个结构体
Num int//线程的个数
ok chan bool//线程与函数通信的管道
}
func (t *Wait) Add (n int){//初始化线程个数
t.Num = n
t.ok = make(chan bool , n)
}
func (t *Wait) Done (){//执行完一个线程之后,执行的函数,每执行完一个线程,调用函数,使用通信管道进行传输一个true
t.ok <- true
}
func (t *Wait) Wait () {//等待函数,每次管道中放入一个true,说明,执行完一个线程,t.Num--,如果等于0说明所有线程执行结束
for <- t.ok {
t.Num--
if t.Num == 0 {
break
}
}
}
|
补充:Golang的WaitGroup陷阱
sync.WaitGroup是并发环境中,一个相当常用的数据结构,用来等待所有协程的结束,在写代码的时候都是按着例子的样子写的,也没用深究过它的使用。前几日想着能不能在协程中执行Add()函数,答案是不能,这里介绍下。
陷阱在WaitGroup的3个函数的调用顺序上。先回顾下3个函数的功能:
Add(delta int):给计数器增加delta,比如启动1个协程就增加1。
Done():协程退出前执行,把计数器减1。
Wait():阻塞等待计数器为0。
考一考
下面的程序是创建了协程father,然后father协程创建了10个子协程,main函数等待所有协程结束后退出,看看下面代码有没有什么问题?
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
|
package main
import (
"fmt"
"sync"
)
func father(wg *sync.WaitGroup) {
wg.Add(1)
defer wg.Done()
fmt.Printf("father\n")
for i := 0; i < 10; i++ {
go child(wg, i)
}
}
func child(wg *sync.WaitGroup, id int) {
wg.Add(1)
defer wg.Done()
fmt.Printf("child [%d]\n", id)
}
func main() {
var wg sync.WaitGroup
go father(&wg)
wg.Wait()
log.Printf("main: father and all chindren exit")
}
|
发现问题了吗?如果没有看下面的运行结果:main函数在子协程结束前就开始结束了。
1
2
3
4
5
6
7
|
father
main: father and all chindren exit
child [9]
child [0]
child [4]
child [7]
child [8]
|
陷阱分析
产生以上问题的原因在于,创建协程后在协程内才执行Add()函数,而此时Wait()函数可能已经在执行,甚至Wait()函数在所有Add()执行前就执行了,Wait()执行时立马就满足了WaitGroup的计数器为0,Wait结束,主程序退出,导致所有子协程还没完全退出,main函数就结束了。
正确的做法
Add函数一定要在Wait函数执行前执行,这在Add函数的文档中就提示了: Note that calls with a positive delta that occur when the counter is zero must happen before a Wait.。
如何确保Add函数一定在Wait函数前执行呢?在协程情况下,我们不能预知协程中代码执行的时间是否早于Wait函数的执行时间,但是,我们可以在分配协程前就执行Add函数,然后再执行Wait函数,以此确保。
下面是修改后的程序,以及输出结果。
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
|
package main
import (
"fmt"
"sync"
)
func father(wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("father\n")
for i := 0; i < 10; i++ {
wg.Add(1)
go child(wg, i)
}
}
func child(wg *sync.WaitGroup, id int) {
defer wg.Done()
fmt.Printf("child [%d]\n", id)
}
func main() {
var wg sync.WaitGroup
wg.Add(1)
go father(&wg)
wg.Wait()
fmt.Println("main: father and all chindren exit")
}
|
1
2
3
4
5
6
7
8
9
10
11
12
|
father
child [9]
child [7]
child [8]
child [1]
child [4]
child [5]
child [2]
child [6]
child [0]
child [3]
main: father and all chindren exit
|
以上为个人经验,希望能给大家一个参考,也希望大家多多支持服务器之家。如有错误或未考虑完全的地方,望不吝赐教。
原文链接:https://blog.csdn.net/Xiang_lhh/article/details/115115856