sync.mutex是默认首选,因其开销最低、语义最清晰、出错路径最少;它不区分读写,避免rwmutex的写饥饿与升级死锁,也规避channel同步导致的goroutine阻塞和调试困难。

Go 里处理并发竞争条件,最直接有效的互斥手段就是 sync.Mutex —— 它不是“可选方案”,而是解决共享变量竞态的刚性依赖。
为什么 sync.Mutex 是默认首选,而不是 sync.RWMutex 或 channel?
因为绝大多数写多读少、或读写混合但临界区短小的场景,sync.Mutex 开销最低、语义最清晰、出错路径最少。它不区分读写意图,只做“一进一出”的排他控制,避免了 RWMutex 中写饥饿、升级死锁(RLock → Lock)等隐性风险;也绕开了用 channel 做同步时容易卡住 goroutine、难以调试的问题。
常见错误现象:RWMutex 被误用于高频写场景,导致写协程长期等待;用 chan struct{} 模拟锁但忘记 close 或漏收,造成 goroutine 泄露。
-
sync.Mutex零值即有效,无需显式初始化 - 加锁后必须配对
Unlock(),推荐用defer mu.Unlock(),但注意:若临界区有 panic,defer仍会执行,这点比手动调用更可靠 - 它不可重入:同一个 goroutine 重复调用
Lock()会死锁 —— 这不是 bug,是设计约束
sync.Mutex 的底层状态怎么影响行为?
它的核心字段是 state int32,其中低三位编码锁状态(是否已锁定、是否唤醒中、是否有等待者),高位记录等待 goroutine 数量。无竞争时,仅靠 CAS 修改 state 完成加锁,全程用户态,开销极小;一旦有竞争,才通过 sema uint32 触发系统级信号量休眠/唤醒。
性能提示:频繁争抢同一把锁会迫使大量 goroutine 进入系统调用,拖慢整体吞吐。这不是锁本身慢,而是设计暴露了共享瓶颈。
- 不要把大段 IO 或网络调用塞进
Lock()/Unlock()之间 - 避免在循环内反复加解锁,考虑批量操作后一次性提交
- 用
go run -race能捕获未加锁的并发读写,但无法发现“锁粒度太粗”这类逻辑问题
什么时候该换其他同步原语,而不是硬扛 sync.Mutex?
当出现以下任一情况,说明 sync.Mutex 已成为瓶颈或误用:
- 读操作远多于写操作,且读临界区较长 → 可评估
sync.RWMutex,但务必测试写饥饿表现 - 需要“只执行一次”的初始化(如单例、配置加载)→ 直接用
sync.Once,比手写双重检查 +Mutex更安全简洁 - 多个 goroutine 需等待某个条件成立(不止是“锁释放”)→ 用
sync.Cond,但它必须和Mutex组合使用,不能单独存在 - 只是原子增减整数或指针 →
atomic包比锁快一个数量级,且无阻塞
真正难的从来不是“怎么加锁”,而是判断“哪段代码属于临界区”以及“这个变量是否真需要被多个 goroutine 共享”。过度保护和保护不足一样危险。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











