sync包是具明确语义与陷阱的并发原语集合,Mutex需成对Lock/Unlock并用defer防漏;RWMutex仅读多写少时有益且不可读锁升级;WaitGroup的Add必须在go前调用;Once.Do确保初始化仅执行一次。

sync 包不是“加个锁就完事”的工具,它是一组有明确语义、适用边界和行为陷阱的并发原语。用错类型、拿错时机、漏释放锁,都会导致死锁、饥饿或竞态——而这些问题在线上环境极难复现。
sync.Mutex 的 Lock/Unlock 必须成对且 defer
最常见错误是忘记 Unlock(),或在多个 return 路径中漏掉它。
- 永远用
defer mu.Unlock(),不要手动在 return 前调用 -
Lock()后 panic 会导致锁永远不释放,必须配合defer才能兜底 - 同一个 goroutine 重复调用
Lock()会死锁(Mutex不可重入) - 不要在锁持有期间做 I/O、网络调用、长耗时计算——这会阻塞其他 goroutine
sync.RWMutex 的读锁不能升级为写锁
这是高频死锁来源:试图在已持 RLock() 的 goroutine 中再调 Lock(),会永远卡住。
-
RLock()和Lock()是互斥的:写锁等待所有读锁释放,读锁也等待写锁释放 - 没有“升级”机制;需要先
RUnlock(),再Lock()(但此时数据可能已被其他 goroutine 修改) - 读多写少场景才用
RWMutex;如果写操作占比超 15%,性能反而不如普通Mutex -
RWMutex的RLock()不会阻塞其他RLock(),但会阻塞后续所有Lock()
sync.WaitGroup 的 Add 必须在 Go 前调用
WaitGroup 计数器不是线程安全的——Add() 必须在 go 语句之前执行,否则可能漏计数。
- 不能在 goroutine 内部调
wg.Add(1)后再做工作,因为Wait()可能早已返回 -
Done()等价于Add(-1),但更语义清晰;务必确保每个Add(1)都有对应Done() -
WaitGroup不能被复制:变量传递要传指针,比如func worker(wg *sync.WaitGroup) - 计数器归零后,
WaitGroup可被重用,但需确保没有 goroutine 正在调Done()
sync.Once 的 Do 是真正“只执行一次”的保障
它比手写 if !initialized { ...; initialized = true } 安全得多,底层用原子操作+互斥锁双重保障。
立即学习“go语言免费学习笔记(深入)”;
-
Once.Do(f)中的f如果 panic,Do会传播 panic,且该Once实例标记为“已完成”——后续调用不再执行f,也不会再 panic - 适合做全局配置加载、单例初始化、信号处理器注册等一次性动作
- 不要把带副作用的函数传给
Do并期望多次触发;它的设计目标就是“无论多少 goroutine 并发调,f 最多执行一次”
Mutex 还是 RWMutex?该用 WaitGroup 还是 channel 关闭通知?该让锁包住整个函数体,还是只锁关键字段赋值?这些决策点没有银弹,依赖对数据访问模式的真实观测。


















