Go并发同步:WaitGroup与Channel详解

技术 · 11 天前 · 43 人浏览

在 Go 并发编程中,sync.WaitGroupChannel 是两大核心同步机制。它们分别适用于不同场景,复杂场景下常需组合使用。本文将深入探讨两者的使用模式、常见陷阱及组合实践。

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. 阻塞等待所有任务完成
}

关键注意事项

  1. Add 的位置:必须在启动 goroutine 前调用 Add()
  2. 变量捕获:循环中使用局部变量避免闭包捕获同一变量
  3. 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 { ... }

通道关闭的黄金法则

  1. 发送方负责关闭:避免向已关闭通道发送数据
  2. 接收安全:关闭后可继续接收剩余数据
  3. 零值机制:关闭后接收返回零值

    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)
    }
}

生产级最佳实践

  1. 缓冲通道优化:根据任务量设置合理缓冲区

    results := make(chan Result, taskCount)
  2. 多通道管理:结合 select 处理多种消息类型
  3. 超时控制:搭配 context 实现超时取消
  4. 错误传递:专用 error 通道收集错误

总结对比

特性WaitGroupChannel
主要用途协程组完成等待协程间通信
同步方式计数屏障数据传递
适用场景并行任务组生产者-消费者
组合优势简单任务同步复杂数据流控制

黄金法则

  • 纯同步需求 → WaitGroup
  • 数据传递需求 → Channel
  • 复杂工作流 → WaitGroup + Channel + Context
Golang WaitGroup Channel