sync.Mutex必须作为结构体字段并用指针接收者,值接收者、局部声明或漏Unlock均导致锁失效;需配对使用Lock/Unlock(推荐defer),读多写少优先RWMutex。

Go里加互斥锁不是“调个Lock()就完事”,真正起作用的前提是:锁必须和被保护的数据绑定在同一生命周期内,且只能通过指针访问——值接收者、局部声明、漏Unlock(),三者任一都会让锁失效。
sync.Mutex 必须作为结构体字段 + 指针接收者使用
常见错误是把sync.Mutex定义在方法内部或用值接收者:
-
func (u User) Update() { u.mu.Lock() ... }→u是副本,u.mu锁的是临时拷贝,原User对象完全不受影响 -
func handler() { var mu sync.Mutex; mu.Lock() ... }→ 每次调用都新建一个锁,无法跨 goroutine 保护共享状态
正确做法是:把mu sync.Mutex作为结构体字段,并用指针接收者声明方法:
type Counter struct {
mu sync.Mutex
value int
}
<p>func (c *Counter) Inc() {
c.mu.Lock()
defer c.mu.Unlock()
c.value++
}
</p>
所有 Lock() 后必须配对 Unlock(),panic 也不能漏
sync.Mutex不支持重入,也不自动恢复。一旦Lock()后因return、panic或逻辑分支没走到Unlock(),后续 goroutine 就永久阻塞。
- 用
defer mu.Unlock()最稳妥,但必须紧跟mu.Lock()之后,不能隔行或放在条件分支里 - 多个提前返回点?用小作用域包住:
{ mu.Lock(); defer mu.Unlock(); /* work */ } -
go run -race能检测竞态,但不会报“漏 unlock”——得靠代码审查或go vet(部分模式有提示)
读多写少时优先用 sync.RWMutex
如果结构体有大量Get()类只读方法,但只有少数Set()类写操作,用sync.Mutex会让所有 goroutine(包括纯读)排队,性能浪费严重。
-
RLock()允许多个 goroutine 并发读,Lock()仍保证写独占 - 注意:
RLock()后必须配RUnlock(),不能混用Unlock() - 不要为了“看起来更细粒度”而滥用——RWMutex 内部开销略高于 Mutex,读写比例不明显时反而更慢
sync.Mutex 零值可用,但绝不能复制
sync.Mutex的零值就是未锁定状态,所以声明时不需new或&sync.Mutex{}。但一旦使用过,就不能再复制:
- 结构体含
sync.Mutex字段 → 该结构体不可复制(比如传参用值类型、append切片时浅拷贝) - 函数返回含
sync.Mutex的结构体?编译期不会报错,但运行时可能引发数据竞争或静默失效 - 包级变量或结构体字段是安全的;局部 new 出来的
*sync.Mutex几乎总是错的
最易被忽略的一点:锁本身不是魔法,它只管“谁在临界区”,不管“临界区里干了什么”。在锁内做 HTTP 请求、数据库查询、或长时间循环,会卡住所有等待者——锁的粒度和临界区行为,比会不会加锁更重要。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











