sync.mutex必须配defer unlock,否则易漏解锁致死锁;rwmutex不支持读锁升级写锁,因会自我阻塞;性能上仅读多写少(如读:写>10:1)时rwmutex更优。

竞态条件不是“可能出问题”,而是“只要没锁,就一定出问题”——哪怕只有一行 counter++,在多个 goroutine 下也必然丢失更新。
为什么 sync.Mutex.Lock() 必须配 defer mutex.Unlock()
不加 defer 的解锁极易漏写或提前返回时跳过,导致后续所有 goroutine 在 mutex.Lock() 处永久阻塞。这不是延迟,是死锁。
- 函数中途
return、panic 或 error 退出时,Unlock()不会执行 -
mutex是值类型,传参后复制,原锁未被释放(常见于方法接收者误用指针) - Go 的
defer在函数 return 前按栈逆序执行,天然兜底
正确写法:
func increment() {
mutex.Lock()
defer mutex.Unlock() // 这行必须紧接 Lock 后,且不能换行到其他逻辑块里
counter++
}
sync.RWMutex 的 RLock() 不能升级为 Lock(),为什么?
Go 的 RWMutex 不支持读锁→写锁的“升级”,因为这会引发死锁:当前 goroutine 持有读锁,又去请求写锁,而写锁需等待所有读锁释放 —— 包括它自己持有的那个。
- 试图在已
RLock()的 goroutine 中调用Lock(),会永远阻塞 - 也不能先
RUnlock()再Lock(),中间存在竞态窗口:其他 goroutine 可能在此间隙修改数据 - 真正安全的做法是:先完整释放读锁,再以写锁重入临界区,且确保业务逻辑允许两次访问间状态不变(或加额外校验)
典型错误模式:
mu.RLock()
if needUpdate {
mu.RUnlock() // 错!此处释放后别人可能已改数据
mu.Lock() // 再锁,但数据已不是刚才看到的了
// ... update
mu.Unlock()
}
Mutex 和 RWMutex 性能差异在哪?别盲目换 RWMutex
RWMutex 并非“更快的 Mutex”,它只在**读多写少**(比如缓存查多改少、配置热加载)且读操作耗时明显时才有收益;否则反而因额外字段和信号量开销更慢。
- 读操作:RWMutex 的
RLock()是原子计数器增减,无系统调用;Mutex 的Lock()在无竞争时也极快,差别不大 - 写操作:RWMutex 的
Lock()需等待所有活跃读锁结束,比 Mutex 更重;且写锁期间新读请求会被挂起,加剧延迟 - 实测建议:当读:写 > 10:1 且读操作 > 100μs 时,才值得考虑 RWMutex
sync.Mutex 的零值可用,但 sync.RWMutex 的零值也安全吗?
是的,两者零值都安全可直接使用——var mu sync.Mutex 和 var rwmu sync.RWMutex 都是合法、未锁定的初始状态。但关键陷阱在于:它们都不能被复制。
- 结构体中嵌入
sync.Mutex时,若该结构体被赋值或传参(非指针),锁状态会丢失 - 切片 append、map 赋值、JSON 反序列化等隐式复制场景,都会触发值拷贝,导致原锁失效
- 最稳妥方式:所有含锁字段的结构体,一律用指针传递;导出字段避免直接暴露锁变量
反例:
type Cache struct {
mu sync.RWMutex // ❌ 导出字段 + 值类型,极易被外部复制
data map[string]int
}
// user := Cache{}; another := user // mu 被复制,两个实例用的是不同锁
真正难的从来不是“怎么加锁”,而是判断哪段是临界区、谁在什么时候持有哪把锁、以及复制发生在哪里——这些地方没有编译错误,只有线上资损。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











