必须用sync.mutex的场景是多个goroutine同时访问同一变量且至少有一个写操作,如count++、结构体字段更新、map/切片并发修改、状态标志位切换;锁必须作为结构体字段用指针接收者,lock后立即defer unlock。

Go 里用 sync.Mutex 保护临界区,不是“可选优化”,而是防止数据错乱的底线操作;没加锁就并发读写共享变量,go run -race 几乎必报 data race。
什么时候必须用 sync.Mutex?
只要多个 goroutine 同时访问同一变量,且其中至少有一个是写操作,就必须加锁。典型场景包括:
- 计数器累加(如
count++)——这不是原子操作,底层是读→改→写三步 - 结构体字段更新(如用户余额
user.Balance += amount) - 切片或 map 的并发修改(即使只读+写混合也不安全,
map尤其敏感) - 状态标志位切换(如
isRunning = true配合后台 goroutine 控制)
注意:sync.Mutex 的零值是有效未锁定状态,直接声明即可用,但绝不能复制(比如作为函数参数传值),否则锁失效——必须传指针或作为结构体字段嵌入。
Lock() 和 Unlock() 的正确配对方式
最常见错误是忘记 Unlock(),或在多个 return 路径中遗漏。正确做法只有一条:用 defer mu.Unlock(),且必须紧跟在 mu.Lock() 后面。
示例:
func (a *Account) Deposit(amount int) {
a.mu.Lock()
defer a.mu.Unlock() // ✅ 这里 defer 才能覆盖所有退出路径
a.balance += amount
}
反例:
- 在
if err != nil { return }前没defer Unlock()→ 锁永远不释放 - 把
defer放在函数末尾(不在Lock()紧后)→ 可能被提前 return 绕过 - 在持有锁期间调用可能 panic 的函数,又没 recover →
defer不触发,锁泄露
sync.Mutex 不能解决什么?
它只管“谁可以进”,不管“进来了做什么”。常见误用包括:
- 在锁内做网络请求、文件 I/O 或长循环计算 → 其他 goroutine 长时间阻塞,吞吐骤降
- 试图用它保护 channel 操作 —— channel 本身线程安全,不需要额外锁
- 和
sync.Cond混用但跳过检查条件循环(即没遵循 lock→check→wait/modify→signal→unlock)→ 唤醒丢失或虚假唤醒 - 期望它提供读写分离能力 —— 这是
sync.RWMutex的职责,Mutex对读写一视同仁
性能上,sync.Mutex 无超时、无重入、不保证公平,高争抢下可能引发饥饿(某个 goroutine 总是抢不到锁)。若需控制等待行为,得换 TryLock() 或结合 time.After 自行实现超时逻辑。
容易被忽略的初始化与生命周期问题
sync.Mutex 必须在首次使用前完成初始化,但它的零值就是可用状态,所以不需要显式调用构造函数。真正容易出错的是:
- 在结构体中定义为字段,却在方法里用值接收者(
func (a Account) Foo())→ 实际操作的是副本,锁完全无效 - 把 mutex 作为局部变量在 goroutine 内声明 → 每个 goroutine 拥有独立锁,毫无互斥意义
- 在测试中复用全局 mutex 变量但没重置状态 → 前一个 test case 没 unlock 导致后续 test panic
最稳妥的方式:mutex 始终作为结构体指针字段存在,所有方法用指针接收者,并在结构体初始化时一并创建(如 &Account{mu: sync.Mutex{}, balance: 0})。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











