结论:共享资源同步需按场景选机制——waitgroup用于等待完成,mutex保护共享变量读写,channel用于通信传数据;用错会导致死锁或竞态,而非性能问题。

直接说结论:共享资源同步不是“选一个机制就行”,而是看场景——sync.WaitGroup管“等完成”,sync.Mutex管“别抢着改”,channel管“按顺序传数据”。用错就死锁或竞态,不是性能问题,是逻辑错误。
WaitGroup 适合什么场景
只关心“所有 goroutine 是否跑完了”,不关心中间状态、不涉及共享变量修改。比如批量拉取 API、并行处理文件列表、启动多个 worker 后统一收尾。
-
wg.Add(1)必须在go语句之前调用,否则可能漏计数(尤其是循环中启 goroutine 时) -
defer wg.Done()要放在 goroutine 函数最开头,确保 panic 时也能减计数 - 不要在
wg.Wait()后再读写被 goroutine 修改的变量——除非你额外加了Mutex或其他保护,否则仍是竞态 - WaitGroup 不是锁,它不保护任何变量,只做计数等待
Mutex 保护共享变量的典型误用
常见错误是“锁了但没锁全”:比如只在写的时候加锁,读的时候裸奔;或者多个字段共用一把锁,但某次更新只锁了其中一个字段。
- 所有对共享变量的读写操作,只要可能并发发生,就必须走同一把
mutex.Lock()/mutex.Unlock()包裹 - 避免在锁内做耗时操作(如 HTTP 请求、
time.Sleep),否则会阻塞其他 goroutine - 不要复制含
sync.Mutex的 struct,因为 mutex 不可拷贝——编译会报cannot copy sync.Mutex - 如果只是高频读、低频写,考虑用
sync.RWMutex,读操作用RLock()/RUnlock(),允许多个并发读
Channel 不是用来“同步”的,是用来“通信”的
很多人用 done chan bool 等 goroutine 结束,这能 work,但属于“借通信实现同步”,容易出错:缓冲区大小难估、发送顺序依赖、无法传递错误信息。
- 无缓冲 channel(
make(chan int))天然同步:发送阻塞直到有人接收,适合配对协作(如轮流打印) - 带缓冲 channel(
make(chan int, 10))不是同步工具,是解耦缓冲——别指望靠它“等完成” - 用 channel 做同步时,务必确保每个 goroutine 都发一次、主 goroutine 都收一次,漏一个就死锁
- 真正需要通信时(比如传结果、传错误、传控制信号),channel 是首选;仅需等待完成,请优先用
WaitGroup
最容易被忽略的一点:同步原语本身不能解决逻辑竞争。比如两个 goroutine 都在 Mutex 保护下递增同一个计数器,结果是对的;但如果它们各自读-改-写,且中间有别的 goroutine 插入修改,那光靠锁也不够——得用原子操作或重新设计状态流转。同步只是工具,关键还是对共享状态变更路径的清晰建模。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











