方便自己随时复习
示例代码
// counter5 一个线程安全的计数器
type counter5 struct {
mu sync.RWMutex
count uint64
}
// incr 使用写锁保护
func (c *counter5) incr5() {
c.mu.Lock()
c.count++
c.mu.Unlock()
}
// count 使用读锁保护
func (c *counter5) count5() uint64 {
c.mu.RLock()
defer c.mu.RUnlock()
return c.count
}
笔记
- 遇到明确区分reader和writer的场景,且有大量的并发读、少量的并发写,并且有强烈的性能需求,就可以考虑使用读写锁
- Go标准库的RWMutex是基于Mutex实现的
- 读写操作的优先级分为三类
- read-preferring:读优先,可能导致写饥饿。如果有大量的读,这种设计会导致所有的读都释放了之后,写才可能获取到锁
- write-preferring:写优先,,意味着如果有一个writer在等待请求锁的时候,它会阻塞新来的请求锁的reader获取到锁,优先保障writer。写优先设计中的优先权是针对新来的请求而言的,主要避免writer的饥饿问题
- 不指定优先级
- Go标准库中的读写锁是写优先,一个正在阻塞的lock调用会排除新的reader请求到锁
- 当一个或者多个reader持有锁的时候,竞争锁的writer会等待reader释放锁,才可能持有这把锁
- 当writer请求锁的时候,是无法改变既有的reader持有锁的现实的,也不会强制这些reader释放锁,他的优先权知识限定后来的reader不要和他抢
- readerCount不仅仅承担着reader的技术功能,还能够表示当前是否有writer竞争或持有锁。是负值的时候,意味着此时有writer等待请求锁,因为writer优先级高,所以把后来的reader阻塞休眠
- mutex可以避免多个writer之间的竞争
- 一旦一个writer获得了内部的互斥锁,就会反转readerCount字段,把它从原来的正整数修改为负数,让这个字段既保存了reader的数量,又表示当前有writer
- 当一个writer释放锁的时候,会再次反转readerCount字段
- 踩坑点
- 不可复制
- 重入导致死锁
- 释放未加锁的RWMutex
源码字段
type RWMutex struct {
w Mutex // held if there are pending writers
writerSem uint32 // 用于writer等待读完成排队的信号量
readerSem uint32 // 用于reader等待写完成排队的信号量
readerCount int32 // 读锁的计数器
readerWait int32 // 等待读锁释放的数量
}