1. 为什么需要并发控制原语
在Go语言开发中,我们经常遇到需要协调多个goroutine执行的场景。当多个goroutine同时访问共享资源时,如果没有适当的同步机制,就会导致数据竞争和不一致的问题。想象一下,多个快递员同时往同一个快递柜里放包裹,如果没有排队机制,最后谁放了什么包裹都会乱套。
Go标准库提供了几种基础的并发控制原语:
- sync.Mutex:互斥锁,保证同一时间只有一个goroutine能访问临界区
- sync.RWMutex:读写锁,允许多个读操作同时进行
- sync.WaitGroup:等待一组goroutine完成
- sync/atomic:原子操作包
这些原语各有适用场景,今天我们要重点讨论的是WaitGroup和原子操作的组合使用,以及如何用对象池来优化这种场景下的性能。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. WaitGroup的工作原理与使用模式
2.1 WaitGroup的基本用法
WaitGroup是Go中最常用的并发同步工具之一,它的API非常简单:
go复制var wg sync.WaitGroup
wg.Add(1) // 增加计数器
go func() {
defer wg.Done() // 减少计数器
// 执行任务
}()
wg.Wait() // 等待计数器归零
WaitGroup内部维护了一个计数器:
- Add(n):增加n个待完成任务
- Done():完成1个任务(相当于Add(-1))
- Wait():阻塞直到计数器归零
2.2 WaitGroup的典型使用场景
WaitGroup特别适合以下场景:
- 主goroutine需要等待所有工作goroutine完成
- 需要并行执行多个独立任务并收集结果
- 实现批量任务的超时控制(结合context)
比如我们要并发下载多个网页:
go复制func fetchURLs(urls []string) {
var wg sync.WaitGroup
for _, url := range urls {
wg.Add(1)
go func(u string) {
defer wg.Done()
resp, err := http.Get(u)
// 处理响应
}(url)
}
wg.Wait()
}
2.3 WaitGroup的常见陷阱
在使用WaitGroup时,新手常犯的错误包括:
- Add调用时机不对:应该在启动goroutine前调用Add,而不是在goroutine内部
- 忘记调用Done:务必使用defer来确保Done被调用
- 计数器溢出:Add的参数总和必须等于Done的调用次数
- Wait调用过早:如果在所有Add之前调用Wait,可能导致提前结束
3. 原子操作与WaitGroup的性能优化
3.1 为什么需要原子操作
在
