sync的waitgroup功能
WaitGroup使用多线程时,进行等待多线程执行完毕后,才可以结束函数,有两个选择
channel
waitgroup
首先使用channel
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,0go add(&i , ok)go add(&i , ok)for <- ok {u++if u == 2 {break}}fmt.Println(i)}
但是,如果线程一旦增多,就会导致代码冗余,增加负担,可以使用sync.WaitGroup包
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功能
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函数等待所有协程结束后退出,看看下面代码有没有什么问题?
package main import ( 'fmt' 'sync') func father(wg *sync.WaitGroup) { wg.Add(1) defer wg.Done() fmt.Printf('fathern') 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函数在子协程结束前就开始结束了。
fathermain: father and all chindren exitchild [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函数,以此确保。
下面是修改后的程序,以及输出结果。
package main import ( 'fmt' 'sync') func father(wg *sync.WaitGroup) { defer wg.Done() fmt.Printf('fathern') 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')}
fatherchild [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
以上为个人经验,希望能给大家一个参考,也希望大家多多支持优爱好网。如有错误或未考虑完全的地方,望不吝赐教。
相关文章:
1. 怎么让div+css兼容ie6ie7ie8ie9和FireFoxChrome等浏览器2. requestAnimationFrame使用示例详解3. 基于JavaScript实现图片裁剪功能4. React优雅的封装SvgIcon组件示例5. uniapp自定义验证码输入框并隐藏光标6. 详解JavaScript中原始数据类型Symbol的使用7. JavaScript深拷贝方法structuredClone使用8. uniapp 手机验证码输入框实现代码(随机数、倒计时、隐藏手机号码中间四位)可以直接使用9. 使用Node.js实现Clean Architecture方法示例详解10. Jquery使用原生AJAX方法请求数据