sync.Mutex 失效主因是值接收者导致锁副本、锁作用域错误或未配对 Unlock;必须用指针接收者,将 Mutex 作为结构体字段与数据同生命周期,并确保 Lock/Unlock 成对。

为什么直接在方法里声明 sync.Mutex 会失效
很多人写法是:
func (u User) UpdateName(name string) {
u.mu.Lock() // ❌ 锁的是副本,毫无意义
defer u.mu.Unlock()
u.name = name
}因为 User 是值接收者,u 是结构体拷贝,u.mu 锁的是一次性副本,对原对象完全无影响。必须用指针接收者。
- 值接收者 → 操作副本 → 锁无效
- 指针接收者 → 操作原对象 → 锁才起作用
- 即使结构体里只有几个字段,也别图省事用值接收者配
sync.Mutex
sync.Mutex 必须和共享数据在同一个作用域里
常见错误是把 sync.Mutex 放在函数内部或局部变量中:
func badExample() {
var mu sync.Mutex
mu.Lock() // ❌ 这个锁只对本次调用有效,无法保护跨 goroutine 的共享状态
// ...
}互斥锁必须和被保护的数据生命周期一致 —— 通常作为结构体字段、包级变量或长期存活的对象成员。
- 结构体字段最常用:确保锁和数据绑定在同一实例上
- 包级
var mu sync.Mutex可用于保护全局变量,但要小心锁粒度太粗 - 绝不能在每次调用时 new 一个
sync.Mutex来“临时加锁”
忘记 Unlock 或 panic 导致死锁怎么办
sync.Mutex 不支持重入,且一旦 Lock() 后没 Unlock(),后续 goroutine 就永远卡住。最稳妥写法是 defer mu.Unlock(),但要注意:
func process(data []int) {
mu.Lock()
if len(data) == 0 {
return // ❌ 这里没 unlock!
}
defer mu.Unlock() // ✅ 这行根本不会执行
// ...
}
- 所有
Lock()后必须配对Unlock(),无论函数从哪个分支返回 - 优先用
defer mu.Unlock(),但要确保它在Lock()之后、函数开头就注册 - 如果逻辑复杂(比如有多个提前 return),把
Lock/Unlock包进小作用域更安全:{ mu.Lock(); defer mu.Unlock(); /* work */ } - 运行时可加
-race检测竞态,但它不报“漏 unlock”,得靠代码审查或静态检查工具(如go vet对部分模式有提示)
读多写少场景下,sync.RWMutex 比 sync.Mutex 更合适
如果结构体大部分操作是读(Get 类方法),只有少数是写(Set 类),用 sync.Mutex 会让所有 goroutine 串行排队,哪怕只是读。换成 sync.RWMutex 能允许多个读并发:
type Counter struct {
mu sync.RWMutex
val int
}
<p>func (c *Counter) Inc() {
c.mu.Lock() // 写锁,独占
c.val++
c.mu.Unlock()
}</p><p>func (c *Counter) Get() int {
c.mu.RLock() // 读锁,可并发
defer c.mu.RUnlock()
return c.val
}-
RLock()和RUnlock()成对使用,和Lock()/Unlock()互斥 - 一个写锁阻塞所有新读锁和写锁;一个读锁不阻塞其他读锁,但会延迟新写锁获取
- 不要嵌套使用读锁和写锁(比如在
RLock()里再Lock()),会死锁
实际用的时候,最容易被忽略的是锁的作用域和接收者类型 —— 它俩一错,整个锁就形同虚设,而且问题还难复现。


















