package myrwmutex
import (
"sync/atomic"
"sync"
)
// go实现的读写锁是写优先,即有写时,会先等待已有的读锁释放,在此写之后的读写操作都必须等此写操作完成
// 而另一种读优先,是指写到达时,之后又有读到达,则如果此时之前的读仍持有锁,则允许继续读操作
type RWMutex struct {
w sync.Mutex // 互斥锁解决多个writer的竞争, 即writer线程间是互斥的
writerSem uint32 // writer信号量
readerSem uint32 // reader信号量
readerCount int32 // reader的数量,以及是否有writer竞争锁
readerWait int32 // writer等待完成的reader的数量
}
const rwmutexMaxReaders = 1 << 30 // 最大的读者数量
// semaAcquire
func semaAcquire(num *uint32, lifo bool, skipframes int) {
}
// semaRelease
func semaRelease(num *uint32, lifo bool, skipframes int) {
}
// RLock 获取读锁
func (rw *RWMutex) RLock() {
// 读者数量+1,readerCount为负值表示此时有write等待请求锁,因为writer优先级高,所以把后来的reader阻塞休眠
if atomic.AddInt32(&rw.readerCount, 1) < 0 {
semaAcquire(&rw.writerSem, false, 0) // 等待写锁释放
}
}
// RUnlock 释放读锁
func (rw *RWMutex) RUnlock() {
if atomic.AddInt32(&rw.readerCount, -1) < 0 {
rw.rUnlockSlow() // 有等待的writer
}
}
func (rw *RWMutex) rUnlockSlow() {
// 将readerWait(表示writer到来时有多少个reader)-1,为0表示所有reader都释放了
if atomic.AddInt32(&rw.readerWait, -1) == 0 {
semaRelease(&rw.writerSem, false, 1)
}
}
// Lock 获取写锁
func (rw *RWMutex) Lock() {
// 首先解决其他writer竞争问题
rw.w.Lock()
// 反转readerCount,表明有writer在竞争锁
r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders
// 如果当前有reader持有锁,那么需要等待
if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 {
semaAcquire(&rw.writerSem, false, 1)
}
}
// Unlock 释放写锁
func (rw *RWMutex) Unlock() {
r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders)
for i := 0; i < int(r); i++ {
semaRelease(&rw.writerSem, false, 0)
}
rw.w.Unlock()
}
//尝试获取
func(rw *RWMutex) TryRLock() {
}
中心思想:writer线程之间互斥,但读和读质检不互斥。
readerCount记录当前有多少个reader,一旦有writer尝试获得锁,此值变为负数(即n-Max),同事将此值记录到readerWait上,则后来的reader观察到此值为负时就等待。
reader释放锁时,readerCount自减,如果为负数则readerWait也自减,当n个reader全部释放后,
readerCount变为n-Max-n即-Max,readerWait为0,则释放writerSem信号量,使等待的writer获得锁返回。
当writer释放锁后,readerCount反转(即r+Max),这个r最开始为-Max,当writer占有锁时,如果有新reader进来,会将r加一,同时发现为负值则等待。假设中间有m个新reader,则writer释放锁时,
readerCount为-Max+m+Max,同时释放m个readerSem 信号量,m个读者同时获得锁。
疑惑点:
这里readerCount反转是通过-Max得到的,那么要求readerCount的数量不超过Max,规定Max为2<<30
readerCount为int32类型,能表示的整数范围在负的2的31次方到正的2的31次方-1,因为有一位是符号位,总的值范围是2的32次方。
查看代码,当readerCount为Max或Max-1或Max-1时,此时来的writer,则readerCount变为0或-1或-2,
则新来的reader第一步先readerCount加一,得到1或者0或者-1,只有结果为负数时才会阻塞休眠,那么1和0的情况会直接获得锁,这种情况是正确的吗?
包内扩展方法
// TryRLock 尝试获取读锁
func(rw *RWMutex) TryRLock() bool {
// TryRLock失败时不能影响内部的任何值,因此必须用CompareAndSwapInt32函数
old := atomic.LoadInt32(&rw.readerCount)
if old < 0 {
return false
}
return atomic.CompareAndSwapInt32(&rw.readerCount, old, old+1)
//if atomic.AddInt32(&rw.readerCount, 1) < 0 {
// return false
//}
//return true
}
// IsPendingWriter 是否有writer
func(rw *RWMutex) IsPendingWriter() bool {
old := atomic.LoadInt32(&rw.readerCount)
if old < 0 {
return true
}
return true
}
// GetReaderCnt 获取reader数量,包含正在持有锁的reader和后来的reader
func(rw *RWMutex) GetReaderCnt() int32 {
old := atomic.LoadInt32(&rw.readerCount)
if old < 0 {
return old + rwmutexMaxReaders
}
return old
}
// GetWaitReaderCnt 获得持有锁的reader
func(rw *RWMutex) GetWaitReaderCnt() int32 {
old := atomic.LoadInt32(&rw.readerWait)
return old
}
包外扩展方法
package hacker
import (
"sync"
"sync/atomic"
"unsafe"
)
type RWMutex struct {
m sync.RWMutex // 互斥锁解决多个writer的竞争, 即writer线程间是互斥的
}
const rwmutexMaxReaders = 1 << 30 // 最大的读者数量
// TryRLock
尝试获取读锁
func(rw *RWMutex) TryRLock() bool {
// TryRLock失败时不能影响内部的任何值,因此必须用CompareAndSwapInt32函数
// readerCount前有一个Mutex和两个uint32, 在不可虑结构体对齐的情况下
readerCountAddr := (*int32)(unsafe.Pointer(uintptr(unsafe.Pointer(&rw.m)) + unsafe.Sizeof(sync.Mutex{}) + 2*unsafe.Sizeof(uint32(0))))
old := atomic.LoadInt32(readerCountAddr)
if old < 0 {
return false
}
return atomic.CompareAndSwapInt32(readerCountAddr, old, old+1)
}
读写锁使用的问题:
1.读写锁用在什么样的场景?(或读写锁主要用来解决什么问题,说说对读写锁的理解?)
在多读少写的场景下,读写锁有比互斥锁更好的性能。
它允许多个同时读,但不允许同时写或同时读写。
有读优先和写优先两种:
在读持有锁时,新写到来,如果再来的读允许获得锁,则是读优先;如果后来的读要等写完成才可以,则是写优先。读优先可能会产生写的饥饿现象。
2.说说RWMutex的实现原理?说说RWMutex与Mutex的区别?
RWMutex内部利用Mutex实现写和写的互斥,其他则使用readerCount、readerWait来标记reader数量、写等待的reader数量,另外两个信号量用来同步。
每次读时readerCount加一,每次读释放readerCount-1,写到来时会将readerCount反转。读者判断为负值则等待。内部主要用add原子操作。
Mutex用一个state标志一个sema信号量,根据state各个bit位标记是否持有锁、等待锁的请求树、唤醒数、饥饿情况等。主要使用CompareAndSwap原子操作。
3.RWMutex源码看过吗?如果使用Mutex来设计一个RWMutex你有什么思路?
4.在执行Lock,Unlock,Rlock,RUnlock时需要考虑什么问题?
Lock时要将readerCount置为负,并写入readerWait数,同时等待读者全部释放。
Unlock是要将eaderCount置为正,将写期间的读请求从阻塞休眠状态唤醒。
Rlock判断readerCount正负值,负数表示有写准备持有锁,要阻塞休眠等待。
RUnlock是要判断readerCount正负值,负数表示有写准备持有锁,要将readerWait自减,减到0表示所有读锁释放,需要唤醒等待的写请求。
5.使用读写锁时有哪些注意点,如何规避死锁问题?
内部有状态值,因此不可复制
内部使用Mutex,因此不可重入
重入会导致死锁
在reader中调用写操作(它会调用 Lock 方法),会形成死锁
writer 依赖活跃的 reader,活跃的reader依赖新来的reader,新来的reader依赖writer,也死锁。
减少锁的粒度、控制加锁时限、注意加锁顺序。
6.如何监控读写锁的等待情况?你有什么思路?
查看readerCount和readerWait值,readerCount为负表示有写等待,此时readerWait是等待读者释放锁的数量,新来的读者数量是readerCount+Max-readerWait
死锁产生的条件
(1)互斥条件:一个资源每次只能被一个进程使用。
(2)请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3)不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4)循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。