前言

如果你有一个任务可以分解成多个子任务进行处理,同时每个子任务没有先后执行顺序的限制,等到全部子任务执行完毕后,再进行下一步处理。这时每个子任务的执行可以并发处理,这种情景下适合使用 sync.WaitGroup

虽然 sync.WaitGroup 使用起来比较简单,但是一不留神很有可能踩到坑里。

sync.WaitGroup 正确使用

比如,有一个任务需要执行 3 个子任务,那么可以这样写:

  1. func main() {
  2. var wg sync.WaitGroup
  3. wg.Add(3)
  4. go handlerTask1(&wg)
  5. go handlerTask2(&wg)
  6. go handlerTask3(&wg)
  7. wg.Wait()
  8. fmt.Println("全部任务执行完毕.")
  9. }
  10. func handlerTask1(wg *sync.WaitGroup) {
  11. defer wg.Done()
  12. fmt.Println("执行任务 1")
  13. }
  14. func handlerTask2(wg *sync.WaitGroup) {
  15. defer wg.Done()
  16. fmt.Println("执行任务 2")
  17. }
  18. func handlerTask3(wg *sync.WaitGroup) {
  19. defer wg.Done()
  20. fmt.Println("执行任务 3")
  21. }

执行输出:

  1. 执行任务 3
  2. 执行任务 1
  3. 执行任务 2
  4. 全部任务执行完毕.

sync.WaitGroup 闭坑指南

01

  1. // 正确
  2. go handlerTask1(&wg)
  3. // 错误
  4. go handlerTask1(wg)

执行子任务时,使用的 sync.WaitGroup 一定要是 wg 的引用类型!

02

注意不要将 wg.Add() 放在 go handlerTask1(&wg) 中!

例如:

  1. // 错误
  2. var wg sync.WaitGroup
  3. go handlerTask1(&wg)
  4. wg.Wait()
  5. ...
  6. func handlerTask1(wg *sync.WaitGroup) {
  7. wg.Add(1)
  8. defer wg.Done()
  9. fmt.Println("执行任务 1")
  10. }

注意 wg.Add() 一定要在 wg.Wait() 执行前执行!

03

注意 wg.Add()wg.Done() 的计数器保持一致!其实 wg.Done() 就是执行的 wg.Add(-1)

小结

sync.WaitGroup 使用起来比较简单,一定要注意不要踩到坑里。

其实 sync.WaitGroup 使用场景比较局限,仅适用于等待全部子任务执行完毕后,再进行下一步处理,如果需求是当第一个子任务执行失败时,通知其他子任务停止运行,这时 sync.WaitGroup 是无法满足的,需要使用到通知机制(channel)。

以上,希望对你能够有所帮助。

推荐阅读