Golang中的sync.WaitGroup详解:原理、用法与避坑指南
在Go语言并发编程中,等待一组goroutine全部执行完成是常见的需求。sync.WaitGroup提供了优雅且线程安全的解决方案。本文将解析其核心机制、正确用法及常见的并发陷阱。
sync.WaitGroup的核心功能与原理
sync.WaitGroup用于等待一组goroutine执行完成。当主协程需要等待所有子协程任务结束后再继续执行后续逻辑时,它是首选工具。它通过维护一个内部计数器来实现同步:
Add(delta int):修改内部计数器。delta参数通常为1(新增任务)或-1(任务结束),支持负数参数调整,但需极度谨慎以避免导致计数器变为负数从而引发Panic。Done():标记某个goroutine已完成执行。它的本质等价于调用Add(-1),减少计数器的值。Wait():当前调用该方法的goroutine会在此处阻塞,直到内部计数器归零。即等待所有调用了Add的任务都成功执行了Done。
值得注意的是,sync.WaitGroup是开箱即用且并发安全的(Concurrent-safe),适合在多协程环境下操作。
使用场景与操作步骤
假设我们需要并行处理N个任务,并在所有任务完成后输出汇总结果。操作流程如下:
第1步:实例化WaitGroup
在主函数或初始化阶段创建变量:
var wg sync.WaitGroup
第2步:启动任务前增加计数器
在每次调用 go 关键字新建一个协程之前,必须调用 wg.Add(1)。这告诉WaitGroup即将有一个新任务加入。
wg.Add(1)
第3步:编写业务逻辑并使用Done收尾
在子协程内部执行具体业务后,务必确保计数器减一。推荐使用 defer wg.Done() 将其放在函数第一行,以保证无论函数是否发生异常退出,计数器都能被正确回收。
func worker(id int) {
defer wg.Done() // 推荐写法,确保资源释放
// 模拟耗时操作
time.Sleep(1 * time.Second)
log.Printf("Worker %d finished\n", id)
}
第4步:主协程调用Wait等待
所有的 go 语句发放完毕后,主协程调用 wg.Wait()。此时程序将暂停执行后续代码,直到所有子协程都执行完 Done。
wg.Wait()
log.Println("All workers completed.")
完整实战代码
以下代码展示了如何同时启动5个goroutine处理数据,待它们全部完成后打印最终日志:
package main
import (
"log"
"sync"
"time"
)
func main() {
// 创建等待组
var wg sync.WaitGroup
// 循环创建5个并发任务
for i := 1; i <= 5; i++ {
// 每个任务开始前增加计数
wg.Add(1)
// 并发执行匿名函数,传入索引i
go func(index int) {
// 确保任务结束后自动将计数减一
defer wg.Done()
// 模拟业务逻辑执行耗时
time.Sleep(1 * time.Second)
log.Printf("第 %d 个goroutine执行完了", index)
}(i)
}
// 阻塞主程序,直到所有goroutine调用过Done()
wg.Wait()
log.Println("所有goroutine都执行完了")
}
必须注意的并发陷阱
尽管API简单,但在实际工程中使用sync.WaitGroup时有几个关键的雷区:
1. 杜绝计数器变负导致的Panic
如果调用 Done() 的次数多于 Add() 的次数,导致计数器变成负数,运行时将触发 panic。因此,请严格保证 Add 和 Done 的数量对应关系,且通常建议在循环外部统一进行Add,或者采用闭包传递计数的方式管理。
2. Wait方法必须在最后调用
Wait() 具有阻塞特性。如果在发送任务(启动 go)之前就调用了Wait,主协程会直接卡死等待永远不会到来的0值信号,从而导致死锁(Deadlock)。
3. 正确管理Done的调用时机
正如上文所说,Done() 必须在 goroutine 彻底结束前执行。若因为业务逻辑提前返回(例如报错退出)而导致 Done() 未被执行,计数器将永远无法归零,导致 Wait() 永久挂起。因此,defer wg.Done() 是最稳健的做法。
以上就是Go语言中同步工具sync.WaitGroup的详细解析与使用指南,更多关于Go高并发编程的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













