在 Go 并发编程中,sync.WaitGroup
和 Channel
是两大核心同步机制。它们分别适用于不同场景,复杂场景下常需组合使用。本文将深入探讨两者的使用模式、常见陷阱及组合实践。
1. WaitGroup:等待协程组完成
WaitGroup
用于等待一组 goroutine 完成执行,是最基础的同步原语。
基础使用模式
func main() {
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1) // 1. 预增加计数
i := i // 避免闭包捕获同一个变量
go func() {
defer wg.Done() // 3. 任务完成时递减计数
fmt.Printf("Worker %d starting\n", i)
}()
}
wg.Wait() // 4. 阻塞等待所有任务完成
}
关键注意事项
- Add 的位置:必须在启动 goroutine 前调用
Add()
- 变量捕获:循环中使用局部变量避免闭包捕获同一变量
- Done 调用:推荐使用
defer wg.Done()
确保执行
常见错误及解决方案
错误类型 | 现象 | 解决方案 |
---|---|---|
Add 在 goroutine 内 | 提前触发 Wait() | 确保在启动 goroutine 前调用 Add() |
WaitGroup 值复制 | 死锁 | 使用指针传递(*sync.WaitGroup ) |
忘记启动 goroutine | 编译错误 | 检查 go func() 语法 |
多次 Wait() | 逻辑混乱 | 保持单 Wait() 调用点 |
2. Channel:协程间通信管道
Channel 是类型安全的通信管道,支持同步和异步操作。
基础通信模式
func main() {
ch := make(chan string) // 无缓冲通道
go func() { ch <- "ping" }()
msg := <-ch // 同步阻塞接收
fmt.Println(msg)
}
核心特性解析
特性 | 说明 | 示例 |
---|---|---|
缓冲通道 | 异步非阻塞通信 | make(chan int, 3) |
通道关闭 | 通知接收方结束 | close(ch) |
方向限定 | 限制通道操作 | func worker(ch chan<- int) |
Range 遍历 | 自动检测关闭 | for v := range ch { ... } |
通道关闭的黄金法则
- 发送方负责关闭:避免向已关闭通道发送数据
- 接收安全:关闭后可继续接收剩余数据
零值机制:关闭后接收返回零值
v, ok := <-ch // ok=false 表示通道已关闭
3. WaitGroup 与 Channel 组合使用
复杂场景常需组合两者实现高效并发控制。
典型死锁案例
// 错误示例:无缓冲通道导致死锁
func main() {
ch := make(chan int)
var wg sync.WaitGroup
for i := 0; i < 2; i++ {
wg.Add(1)
go func() {
ch <- 1 // 阻塞等待接收方
wg.Done()
}()
}
wg.Wait() // 永远阻塞在此
close(ch) // 无法执行到此处
for v := range ch { ... }
}
正确解决方案
func main() {
ch := make(chan int)
var wg sync.WaitGroup
// 生产者
for i := 0; i < 2; i++ {
wg.Add(1)
go func() {
defer wg.Done()
ch <- 1
}()
}
// 独立协程处理关闭
go func() {
wg.Wait()
close(ch) // 确保所有发送完成后再关闭
}()
// 消费者
for v := range ch {
fmt.Println(v)
}
}
生产级最佳实践
缓冲通道优化:根据任务量设置合理缓冲区
results := make(chan Result, taskCount)
- 多通道管理:结合
select
处理多种消息类型 - 超时控制:搭配
context
实现超时取消 - 错误传递:专用 error 通道收集错误
总结对比
特性 | WaitGroup | Channel |
---|---|---|
主要用途 | 协程组完成等待 | 协程间通信 |
同步方式 | 计数屏障 | 数据传递 |
适用场景 | 并行任务组 | 生产者-消费者 |
组合优势 | 简单任务同步 | 复杂数据流控制 |
黄金法则:
- 纯同步需求 → WaitGroup
- 数据传递需求 → Channel
- 复杂工作流 → WaitGroup + Channel + Context