写锁能立刻阻塞新来的读锁,因为RWMutex用readerCount的正负号标记状态:读时为正值(读协程数),Lock()原子减去rwmutexMaxReaders使其变负,后续RLock检测到负值即阻塞等待。

写锁为什么能立刻阻塞新来的读锁
因为 RWMutex 用 readerCount 的正负号做状态标记:正常读时为正值(当前读协程数),一旦有协程调用 Lock(),就会原子地给 readerCount 减去 rwmutexMaxReaders(即 1 ),使其瞬间变负。后续任何 <code>RLock() 都会先执行 atomic.AddInt32(&rw.readerCount, 1),结果仍小于 0,于是直接进入 readerSem 等待队列挂起。
这一步不依赖额外标志位,纯靠整数溢出语义实现——既节省内存,又避免 CAS 竞争。但要注意:这个负值不是错误,而是设计好的“写锁已就绪”信号。
多个读锁共存时,写锁如何知道该等谁
readerWait 字段才是关键:当写锁调用 Lock() 时,它先拿到底层 w 互斥锁(防多个写者竞争),再把当前 readerCount 的绝对值(即正在读的 goroutine 数)拷贝到 readerWait。之后每有一个 RUnlock(),就原子减一 readerWait;直到归零,才通过 writerSem 唤醒等待的写者。
这意味着:
立即学习“go语言免费学习笔记(深入)”;
- 写锁不关心具体是哪些读协程,只计数
- 中途新进的读协程会被
RLock()拦在readerSem外,不影响readerWait的倒计时 - 如果写锁持有期间又有新读请求,它们会排队等
readerSem,而不是加入readerWait
读优先策略的真实表现与陷阱
Go 的 RWMutex 默认是读优先,但不是无条件的:它只在“没有写者等待”时允许新读锁立即进入;一旦有写者在 writerSem 上排队,后续所有读锁都会被挡在 readerSem 外,直到该写者完成。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
这种机制防止了写饥饿,但容易被误读为“完全公平”。实际中常见问题包括:
- 高频写场景下,
RLock()可能长时间阻塞,表现像普通 mutex - 若写操作本身耗时(比如序列化大结构体),会拖慢所有等待的读请求
- 无法通过 API 查询当前是否有写者在等,调试时只能靠日志或 pprof 观察阻塞点
RLock 和 Lock 不能混用升级降级
RWMutex 不支持在持有 RLock() 时直接调用 Lock() 升级,也不支持持 Lock() 后调用 RLock() 降级——两者会互相死锁。
例如:
mu.RLock() // ... 中间想改数据 mu.Lock() // 死锁:自己占着读锁,又想拿写锁
正确做法是先 RUnlock(),再 Lock(),但中间存在竞态窗口。若业务逻辑要求“读-判-写”,应直接上 Lock(),别贪读锁并发。
真正容易被忽略的是:哪怕只是临时想“看看要不要写”,只要用了 RLock(),后面所有升级路径都得重入锁逻辑,没法绕开。

















