Go语言sync.WaitGroup用法上可能会出现的坑
Go
自带的包sync
中有一个工具WaitGroup
,从名字就可以看出,它可以帮助我们控制并发的流程。
Go
的并发是通过goroutine
来处理,有时候我们希望控制某一组routine
的执行,比如全部执行完了,再进行下一步,这时会想到goroutine
的搭档channel
,我们可以通过channel
的阻塞,来完成流程控制。
不过有了sync.WaitGroup
,代码写起来会清晰许多,它的用法非常简单,只有三个方法:
- Add(delta int)
- Done()
- Wait()
通过Add
,增加需要等待的任务数,Done
会将数目减一,而Wait
会将程序阻塞,知道Done
方法将数目降至为0。
这是一个具体的例子:
1 | func main() { |
上面的例子中,for
循环启用了10条goroutine
,而在主程序中,使用wg.Wait()
等待协程的完成。
好了,上面只是铺垫,之所以记录这篇文,主要还是因为看到有文章在介绍WaitGroup时,有隐性的bug
存在。
比如说,把上面的代码改成这样:
1 | func main() { |
这里就会出现问题了,可能n
的数值比较小,运行很多次都未必出现问题,counter
绝大概率下还是会输出10。
但这正是并发程序的可怕之处,运行几个月才出现问题,往往让人一头雾水。
把n
调到100、1000,问题就出现了,我们会发现,wg.Wait()
失灵了,它没等到所有goroutine
执行完毕,就已经接着走了。
问题就在于wg.Add(1)
,我们知道wg.Wait()
只要判断它自身没有在等待的任务,便不锁住程序,那只要goroutine
的执行够快,下一条wg.Add(1)
还来不及运行的时候,就被wg.Done()
抢先一步,自然wg.Wait()
就完成了。
不知道上面的描述你能否理解,其实类似这种问题,可能并非所有人都能一时明白过来,更多的是靠理解与经验,所以并发
容易出问题也是这个原因。
关于WaitGroup
我还看到另外一个记录的问题,就是有些童鞋将它传递到具体的函数里面去执行,然后就发现它不生效了,这个跟Go
的引用与拷贝有关,如果直接传入WaitGroup
,实际上它不是一个引用,而是一份拷贝,那么如果是在里面进行的Add
操作,那必然对原先的参数是不起作用的,这一点需要去看更多关于Go
函数传值的内容。
- 本文链接:https://keepmoving.ren/golang/waitgroup/
- 版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 CN 许可协议。转载请注明出处!