在Golang的并发编程中,我们经常会通过goroutine启动多个协程并行处理任务,比如同时请求多个接口、并行处理多份数据等。但协程是异步执行的,主程序不会自动等待所有协程完成,这时候就需要用到sync包下的WaitGroup工具来实现协程等待。

sync.WaitGroup是什么
sync.WaitGroup是Golang标准库sync包中提供的一个结构体,用于等待一组协程的结束。它的核心逻辑是通过计数器来记录还未完成的协程数量,当计数器归零时,等待的阻塞就会解除。它主要包含三个核心方法:
- Add(int):向计数器添加指定的数值,通常在启动协程前调用,用来标记要等待的协程数量
- Done():将计数器减1,通常在协程执行结束时调用,等价于Add(-1)
- Wait():阻塞当前协程,直到计数器归零,也就是所有需要等待的协程都执行完成
基本使用示例
下面是一个最基础的sync.WaitGroup使用例子,启动3个协程分别处理任务,主程序等待所有协程完成后才输出结束信息:
package main
import (
"fmt"
"sync"
"time"
)
func main() {
// 声明WaitGroup实例
var wg sync.WaitGroup
// 设置要等待的协程数量为3
wg.Add(3)
// 启动第一个协程
go func() {
// 协程结束时调用Done,计数器减1
defer wg.Done()
time.Sleep(1 * time.Second)
fmt.Println("协程1任务完成")
}()
// 启动第二个协程
go func() {
defer wg.Done()
time.Sleep(2 * time.Second)
fmt.Println("协程2任务完成")
}()
// 启动第三个协程
go func() {
defer wg.Done()
time.Sleep(1 * time.Second)
fmt.Println("协程3任务完成")
}()
// 阻塞主程序,等待所有协程完成
fmt.Println("开始等待协程完成")
wg.Wait()
fmt.Println("所有协程已完成,主程序继续执行")
}
运行这段代码会先输出开始等待协程完成,然后等待1到2秒后,三个协程的输出会依次出现,最后输出所有协程已完成,主程序继续执行,说明Wait方法正确阻塞了主程序直到所有协程结束。
常见错误和注意事项
1. Add调用时机错误
一定要在启动协程之前调用Add方法,如果先启动协程再调用Add,可能会出现协程已经执行完Done,但Add还没增加计数器的情况,导致Wait提前返回。比如下面的错误写法:
// 错误示例
go func() {
defer wg.Done()
// 协程逻辑
}()
wg.Add(1) // 启动协程后才Add,可能出现问题
2. 计数器数值不匹配
Add传入的数值需要和实际启动的协程数量一致,同时每个协程都要确保调用Done。如果Add的数量多于实际协程数,Wait会永远阻塞;如果少于实际协程数,可能会导致部分协程还没完成主程序就继续执行。
3. WaitGroup传递问题
如果需要在多个函数间传递WaitGroup,一定要传递指针,因为WaitGroup是结构体,值传递会拷贝一份新的实例,导致计数器不共享,等待逻辑失效。正确写法如下:
func task(wg *sync.WaitGroup) {
defer wg.Done()
// 任务逻辑
}
func main() {
var wg sync.WaitGroup
wg.Add(1)
// 传递WaitGroup的指针
go task(&wg)
wg.Wait()
}
4. 避免复用WaitGroup
WaitGroup在计数器归零后可以复用,但一定要确保所有之前的Wait都已经返回,再重新调用Add。如果复用不当很容易出现计数混乱,建议每次需要等待一组协程时,都重新声明一个新的WaitGroup实例。
实际场景应用示例
比如我们需要同时请求三个不同的接口获取数据,等所有数据都拿到后再做汇总处理,就可以用sync.WaitGroup实现:
package main
import (
"fmt"
"sync"
)
// 模拟接口请求
func requestAPI(apiName string, wg *sync.WaitGroup, result *map[string]string) {
defer wg.Done()
// 模拟请求耗时
// 实际场景中这里会是http请求逻辑
(*result)[apiName] = fmt.Sprintf("来自%s的返回数据", apiName)
}
func main() {
var wg sync.WaitGroup
// 存储结果的map
result := make(map[string]string)
// 要请求的接口列表
apis := []string{"用户接口", "订单接口", "商品接口"}
wg.Add(len(apis))
for _, api := range apis {
// 注意这里要传api的拷贝,避免协程闭包捕获循环变量的问题
apiName := api
go requestAPI(apiName, &wg, &result)
}
wg.Wait()
// 所有接口请求完成后汇总数据
fmt.Println("所有接口请求完成,汇总结果:")
for k, v := range result {
fmt.Printf("%s: %sn", k, v)
}
}
这个例子中我们启动三个协程分别请求三个接口,主程序等待所有请求完成后,再遍历结果map输出所有接口的返回数据,实现了并行请求并等待全部完成的需求。
Golangsync.WaitGroup协程并发编程goroutine修改时间:2026-06-09 02:27:24