waitgroup在golang中,用于线程同步,指等待一个组,等待一个系列执行完成后,才会向下执行,可以解决一个 进程goroutine 等待多个该进程启动的子线程goroutine 同时结束的场景,这个比较常见的场景就是例如 后端 main processer 启动了多个消费者worker干活,还有爬虫并发爬取数据,多线程下载等等,为了保证主进程在所有的子线程完成后再退出,这时就要用上waitgroup
我们这里模拟一个 worker 的例子
package main
import (
"fmt"
"sync"
"time"
)
func worker(index int, w chan struct{}, wg *sync.WaitGroup) {
defer wg.Done()
time.Sleep(1 * time.Second)
fmt.Println(time.Now())
fmt.Println(index)
<-w
}
func main() {
wg := new(sync.WaitGroup)
ws := make(chan struct{}, 3)
for i := 0; i < 1000; i++ {
ws <- struct{}{}
wg.Add(1)
go worker(i, ws, wg)
}
wg.Wait()
}
在这段代码中,main最后一行是
wg.Wait()
这行代码保证有所的100个子线程全部都执行完成后才会退出main函数,如果没有最后一行wg.Wait(),可能会出现遍历完程序就直接结整了,有可能只有不确定个几个子线程执行完成,其它线程由于主线程main退出就直接退出了
从这个例子我们也可以看到 waitgroup通常配合来限制并发线程个数和确保所有的线程都最终都执行完成
这段代码中 ws 有三个缓冲,所以并发的数量是3,超过3个就要等待执行完成释放所占用通道后才能再开新的线程
上面的代码,会在执行到wg.Wait()后等待,直到所有的1000线程全部执行完后才会继续往下执行
可以説这段代码非常精妙
同时我们在使用waitgroup时也要注意一些坑:
1、 Add一个负数
如果计数器的值小于0会直接panic
2、 Add在Wait之后调用
比如一些子协程开头调用Add结束调用Wait,这些 Wait无法阻塞子协程。正确做法是在开启子协程之前先Add特定的值。
3、 未置为0就重用
WaitGroup可以完成一次编排任务,计数值降为0后可以继续被其他任务所用,但是不要在还没使用完的时候就用于其他任务,这样由于带着计数值,很可能出问题。
4、 复制waitgroup
WaitGroup有nocopy字段,不能被复制。也意味着WaitGroup不能作为函数的参数
WaitGroup是Golang应用开发过程中经常使用的并发控制技术,学习golang是我们必须要掌握和理解的机制之一,建议有时间了大家再进一步的研究一下WaitGroup的底层实现逻辑。
如有问题,欢迎大家留言沟通,点赞支持!!!