sync.RWMutex 的 RLock/RUnlock 必须严格成对,漏调用导致读锁不释放、写锁永久阻塞,多调用触发 panic;Lock 阻塞于未释放的读锁而非正在执行的读操作;混用锁需统一获取顺序以防死锁。

读多写少场景下,sync.RWMutex 能显著提升并发读性能;但一旦有写操作在排队,后续所有读请求都会被阻塞——这不是 bug,是设计使然。
为什么 RLock 和 RUnlock 必须成对出现?
Go 的 sync.RWMutex 不做运行时栈追踪,RUnlock 多调用或漏调用都会直接破坏锁状态:前者可能触发 panic(runtime error: sync: RUnlock of unlocked RWMutex),后者导致读锁永远不释放,所有后续 Lock 卡死。
- 只在明确的函数入口/出口配对使用,避免在分支里提前 return 前忘记
RUnlock - 不要在 defer 中无条件
RUnlock,除非你 100% 确保前面一定调用了RLock - 调试时可临时加计数器或用
go tool trace观察锁持有时间,但生产环境别靠日志判断是否配对
Lock 会等所有 RLock 释放,但不会等正在执行的 RLock 持有者结束
这是最常被误解的一点:Lock 阻塞的不是“当前有没有读操作在跑”,而是“有没有读锁还被持有”。只要还有 goroutine 持有 RLock(哪怕它正 sleep 或在 IO),Lock 就得等。
- 典型陷阱:一个长耗时的读操作(比如遍历大 map + HTTP 调用)持着
RLock,此时写请求进来就会卡住,连带后续所有读也被新来的Lock挡住 - 解决思路不是减少
RLock范围,而是拆分——把纯内存读取部分锁住,IO 或计算部分移出临界区 - 注意:
RLock允许嵌套,但Lock是排他且不可重入的;同一个 goroutine 重复Lock会死锁
和 sync.Mutex 混用时,哪些顺序会死锁?
混用本身不禁止,但顺序错乱极易死锁。比如 A goroutine 先 mu.Lock() 再 rw.RLock(),B goroutine 反过来先 rw.Lock() 再 mu.Lock(),就构成经典环形等待。
- 统一锁顺序:约定所有代码中,总是先获取
sync.RWMutex,再获取sync.Mutex(或反之),并在注释里写明 - 避免跨锁做耗时操作:比如在
rw.RLock()里调mu.Lock()后又去发网络请求 - 测试时用
-race能发现部分竞争,但抓不住死锁;真正验证得靠超时控制(如context.WithTimeout包裹锁获取)
真正难处理的从来不是怎么加锁,而是判断哪段数据该用读写锁、哪段该拆成原子变量、哪段其实该换 channel 或 actor 模型——锁只是工具,别让它变成架构的拐杖。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











