章节

10.2 WaitGroup

本篇学习 sync.WaitGroup 的 Add、Done、Wait,并能等待一组 goroutine 执行完成。

WaitGroup

概念说明

sync.WaitGroup 用于等待一组 goroutine 完成。
它内部维护一个计数器:任务开始前增加计数,任务结束后减少计数,主流程等待计数归零。

语法/规则

  1. Add(n) 表示增加 n 个待完成任务。
  2. Done() 表示一个任务完成,通常配合 defer 使用。
  3. Wait() 会阻塞,直到计数器归零。
  4. Add 通常要在启动 goroutine 之前调用。
  5. AddDone 次数必须匹配,否则可能死锁或 panic。

等待多个任务示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
	"fmt"
	"sync"
)

func main() {
	var wg sync.WaitGroup // 声明一个 WaitGroup 变量,变量名 wg 是 WaitGroup 的常见缩写写法

	for i := 1; i <= 3; i++ {
		wg.Add(1) // 每准备启动一个 goroutine,就先把计数器加 1
		go func(id int) {
			defer wg.Done() // 当前 goroutine 执行结束前,把计数器减 1
			fmt.Println("task", id)
		}(i) // 把当前循环里的 i 作为参数传进去,避免多个 goroutine 读到同一个 i
	}

	wg.Wait() // 阻塞等待,直到前面 3 个 goroutine 都调用 Done
	fmt.Println("done")
}

输出结果(任务顺序可能不同):

1
2
3
4
task 1
task 2
task 3
done

怎么理解这个示例

  1. 这段代码的核心流程是:先登记 3 个任务,再并发执行它们,最后统一等待它们全部结束。
  2. done 一定会在所有 task 输出之后出现,因为主 goroutine 会卡在 wg.Wait(),不会提前往下执行。
  3. task 1task 2task 3 的先后顺序不固定,因为 goroutine 的调度顺序不是手动控制的。

场景示例:并发下载多个文件后,再进入下一步

假设程序要同时下载 3 个文件。
下载可以并发进行,但“开始打包”这一步必须等 3 个文件都下载完成后才能继续。

 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
package main

import (
	"fmt"
	"sync"
	"time"
)

func downloadFile(fileName string, wg *sync.WaitGroup) { // 接收 WaitGroup 指针,表示操作的是同一个计数器
	defer wg.Done() // 当前文件下载完成后,把计数器减 1

	time.Sleep(100 * time.Millisecond) // 模拟下载耗时
	fmt.Println(fileName, "downloaded")
}

func main() {
	files := []string{"a.jpg", "b.jpg", "c.jpg"}

	var wg sync.WaitGroup

	for _, fileName := range files {
		wg.Add(1) // 每启动一个下载任务,就先登记 1 个待完成任务
		go downloadFile(fileName, &wg) // 把同一个 WaitGroup 传给每个 goroutine
	}

	wg.Wait() // 等全部文件下载完成后,再继续往下执行
	fmt.Println("start package")
}

输出结果(顺序可能不同):

1
2
3
4
b.jpg downloaded
a.jpg downloaded
c.jpg downloaded
start package

WaitGroup 在这里起到什么作用

  1. 3 个文件可以同时下载,所以前面的下载任务适合用 goroutine 并发执行。
  2. start package 不能太早执行,否则可能还有文件没下载完。
  3. WaitGroup 的作用就是:让主 goroutine 等所有下载任务都完成后,再继续下一步。
  4. 它的独特点是只负责“等全部做完”,不负责传递结果,也不负责控制执行顺序。

常见错误

  1. 在 goroutine 内部调用 Add,主流程可能先执行到 Wait,造成时序问题。
  2. 忘记调用 Done,导致 Wait 一直阻塞。
  3. Done 调用次数多于 Add,会导致计数器变成负数并触发 panic。
  4. 循环启动 goroutine 时没有把循环变量作为参数传进去,容易读到意外值。
本文禁止转载
使用 Hugo 构建
主题 StackJimmy 设计 由 Hobin 魔改
最近构建时间:2026-04-17 19:07:48 CST
载入天数...载入时分秒...
发表了 1 篇文章 · 发表了 152 篇笔记 · 总计 18 万 0 千字