如何在Golang中处理多任务同步_Golang sync包与WaitGroup应用技巧

WaitGroup.Add()必须在启动goroutine前调用,且参数>0;不可在goroutine中调用Add();defer wg.Done()不能漏括号;必须传指针避免复制;不解决数据竞争,共享变量需额外同步。

WaitGroup 必须在启动 goroutine 前调用 Add()

常见错误是先开 goroutine,再在 goroutine 里调用 WaitGroup.Add(1) —— 这会导致 Wait() 可能永远阻塞,或 panic:「negative WaitGroup counter」。因为 Add()Done() 不是原子配对操作,必须由主线程提前声明任务数。

  • Add() 必须在 go 语句之前调用,且参数 > 0
  • 若任务数动态不确定(比如从 channel 收集),需先遍历一次计数,或改用 sync.Map + 计数器
  • 传入 0 给 Add(0) 合法但无意义;传负数直接 panic

别在 goroutine 里 defer WaitGroup.Done() 而不加括号

写成 defer wg.Done(漏掉括号)是静默错误:函数未执行,Done() 被当值传递,最终计数器不减,Wait() 永远不返回。Go 编译器不会报错,运行时行为完全异常。

go func() {
    defer wg.Done() // ✅ 正确:带括号,实际调用
    // ... work
}()
go func() {
    defer wg.Done // ❌ 错误:只是取函数地址,不调用
    // ... work
}()

WaitGroup 不能被复制,跨 goroutine 传递必须用指针

sync.WaitGroup 包含 mutex 等非可复制字段,值拷贝会破坏内部状态。常见于:将 wg 作为参数传给函数却用了值传递、在循环中创建临时 wg 变量。

  • 函数参数类型必须是 *sync.WaitGroup,不是 sync.WaitGroup
  • 切片或 map 中存 wg 也必须存指针:[]*sync.WaitGroup
  • 初始化后不要赋值给另一个变量(如 wg2 := wg),否则后续 Done() 作用于副本,主 wg 无变化

WaitGroup 不解决数据竞争,共享变量仍需额外同步

WaitGroup 只保证 goroutine 执行完毕的时机,不保护读写共享内存。例如多个 goroutine 向同一 slice 追加元

素,即使用了 WaitGroup,仍可能 panic 或丢数据。

  • 写共享变量前,考虑 sync.Mutexsync.RWMutexchannels
  • 优先用 channel 传递结果,而非让 goroutine 直接修改外部变量
  • 简单累加可用 sync/atomic(如 atomic.AddInt64),比锁更轻量

WaitGroup 的真正难点不在语法,而在于它只回答「都做完了吗」,从不回答「做对了吗」——共享状态的正确性,得靠你亲手守住。