长宁建设机械网站,长沙做网站湖南微联讯点不错,现在网站建设需要多少钱,更改wordpress默认登录后台在简略的说之前#xff0c;首先要对RW锁的结构有一个大致的了解
type RWMutex struct {w Mutex // 写锁互斥锁#xff0c;只锁写锁#xff0c;和读锁无关writerSem uint32 // sema锁--用于“写协程”排队等待readerSem uint32 // sema锁--用于“读协程”排队…在简略的说之前首先要对RW锁的结构有一个大致的了解
type RWMutex struct {w Mutex // 写锁互斥锁只锁写锁和读锁无关writerSem uint32 // sema锁--用于“写协程”排队等待readerSem uint32 // sema锁--用于“读协程”排队等待readerCount int32 // 读锁的计数器readerWait int32 // 等待读锁释放的数量
}
这里要额外说一句writerSem和readerSem底层都是semaRoot这个结构体有兴趣可以了解下他的用法有点类似于一个简版的channel很多地方把他的初始值设置为0使得所有想获取该sema锁的协程都排队等待也就是说初始值为0的sema锁他本身起到的作用是成为一个协程等待队列就像没有缓冲区的channel一样。 好现在进入正题。本文是为了在面试中能快速口述RW锁并非为了完整解答RW锁的机制。 前提
readerCount这个参数非常重要
为负数时说明此锁已经被写协程占据所有渴望加读锁的协程被阻塞在readerSem为正数时正数的数值为当前持有该锁的所有读协程的数量总和所有渴望加写锁的协程被阻塞在writerSem
读写锁互斥性
读锁是并发的可以多个协程持有一把读锁。写锁是唯一的互斥的同一时刻只能有一个写协程拥有写锁读锁和写锁是互斥的写锁生效时是不能有读锁被获取到同样必须所有的读锁都被释放或者压根没有读协程获取读锁写锁方可被获取。
一个很重要的参数const rwmutexMaxReaders 1 30 rwmutexMaxReaders 非常大意思是最多能有rwmutexMaxReaders1 30 十进制为 4294967296个协程同时持有读锁。 写锁上锁场景
首先分析写锁因为读锁的很多操作是根据写锁来的如果一上来就说读锁很多东西没法串起来 func (rw *RWMutex) Lock() {// race.Enabled是官方的一些测试性能检测的东西无需关心这个只在编译阶段才能启用if race.Enabled {_ rw.w.staterace.Disable()}// First, resolve competition with other writers.rw.w.Lock()// Announce to readers there is a pending writer.r : atomic.AddInt32(rw.readerCount, -rwmutexMaxReaders) rwmutexMaxReaders// Wait for active readers.if r ! 0 atomic.AddInt32(rw.readerWait, r) ! 0 {runtime_SemacquireMutex(rw.writerSem, false, 0)}if race.Enabled {race.Enable()race.Acquire(unsafe.Pointer(rw.readerSem))race.Acquire(unsafe.Pointer(rw.writerSem))}
} 获取写锁--没有读锁等待 rw.w.Lock进行加锁阻塞后续的其他写协程的锁请求。atomic.AddInt32进行原子操作减去rwmutexMaxReaders减成功才说明没有并发问题可以继续下面的操作。然后再加上rwmutexMaxReaders得到真正的readerCount的数值。此时还需要再次进行一个原子操作把当前readerCount的值搬运到readerWait里面意思是当前要获取写锁的协程需要等待的读锁的数量。此时readerCount只有两种情况一种是0一种是正数因为只有写锁上的时候才为负数而上面的操作已经还原了加写锁之前的值而w.Lock保证了不会有2个及以上的写协程去同时操作readerCount 如果是 0加锁成功。如果不为0则说明有读锁等待详见场景2 获取写锁--有读锁等待 接上面的判断如果readrCount不为0说明前面有读锁正在运行写锁需要等待所有读锁释放才能获取写锁当前协程执行 runtime_SemacquireMutex 进入 waiterSem 的休眠队列等待被唤醒 获取写锁--前面已经有写锁了 后面的写协程也调用 rw.w.Lock() 进行加锁因为前面有写锁已经获取了w所以后续的写协程会因为获取不到w而进入到w的sema队列里面w是一个mutex的锁mutex锁里是一个sema锁sema锁因为没有设置初始值所以退化为一个队列而获取不到w锁的就会直接被阻塞在w的sema队列里从而无法进行接下来的操作写锁释放锁场景
func (rw *RWMutex) Unlock() {if race.Enabled {_ rw.w.staterace.Release(unsafe.Pointer(rw.readerSem))race.Disable()}// Announce to readers there is no active writer.r : atomic.AddInt32(rw.readerCount, rwmutexMaxReaders)if r rwmutexMaxReaders {race.Enable()throw(sync: Unlock of unlocked RWMutex)}// Unblock blocked readers, if any.for i : 0; i int(r); i {runtime_Semrelease(rw.readerSem, false, 0)}// Allow other writers to proceed.rw.w.Unlock()if race.Enabled {race.Enable()}
} 释放写锁--后面【没有】读锁等待 执行atomic.AddInt32进行原子操作把已经为负值的readerCount还原为正数此时已经算释放了写锁此步骤不重要就是个判错如果还原后的readerCount比rwmutexMaxReaders还大这就是说明出错了直接throw弹出错误throw这个方法是内部方法对go来说就是panic了此场景因为没有读锁等待此时的readerCount为0不会进入for循环直接rw.w.Unlock释放w锁允许其他写协程加锁此时其他的写协程会被从w里的sema队列里唤醒 释放写锁--后面【有】读锁等待 接场景1原子操作readerCount释放写锁后如果r是大于0说明有读锁等待for循环readerSem里面所有的等待的读协程因为读锁是共享锁所以所有的读协程都会获取锁并被唤醒rw.w.Unlock释放w锁允许其他写协程加锁其他的写协程会被从w里的sema队列里唤醒 释放写锁--后面有【写锁】等待 上接场景2当rw.w.Unlock释放w锁其他的写协程会被从w里的sema队列里唤醒写锁释放的时候是先唤醒所有等待的读锁再解除rw.w锁所以并不会造成读锁的饥饿后面读锁再次对rw.w进行上锁重复上面所述写锁获取锁的场景读锁上锁场景
func (rw *RWMutex) RLock() {// race.Enabled都是测试用的代码在阅读源码的时候可以跳过if race.Enabled {_ rw.w.staterace.Disable()}if atomic.AddInt32(rw.readerCount, 1) 0 {// A writer is pending, wait for it.runtime_SemacquireMutex(rw.readerSem, false, 0)}if race.Enabled {race.Enable()race.Acquire(unsafe.Pointer(rw.readerSem))}
} 获取读锁--此时没有写锁. 最简单的场景协程对rw.readerCount进行原子操作加一如果得到的结果为正数说明获取读锁成功。 获取读锁--前方已经有写锁抢占了该锁 当协程对rw.readerCount进行原子加1操作的时候发现加完readerCount还是负数说明在这个时间点以前已经有协程获取了写锁 runtime_SemacquireMutex 方法将当前协程加入readerSem队列等待写锁释放后被批量唤醒写锁释放会一次性放出所有的堆积的读协程 获取读锁--前方有写锁抢已经被抢占后方有写锁等待 写锁在获取的时候对RWMutex.w进行加锁是独占锁如果前方一个写锁已经得到了锁正在处理业务那么后方的写锁进来就会发现加不上锁直接在rw.w.lock阶段就阻塞了后面的逻辑是无法继续运行的所以进入不了writerSem它只会进入到w这个mutex锁的sema队列里读锁则进入休眠队列readerSem读锁释放锁场景
func (rw *RWMutex) RUnlock() {if race.Enabled {_ rw.w.staterace.ReleaseMerge(unsafe.Pointer(rw.writerSem))race.Disable()}if r : atomic.AddInt32(rw.readerCount, -1); r 0 {// Outlined slow-path to allow the fast-path to be inlinedrw.rUnlockSlow(r)}if race.Enabled {race.Enable()}
} 释放读锁--后方没有写锁等待 atomic.AddInt32 进行原子操作让readerCount 减1操作后如果readerCount 大于0说明后方是没有写锁等待的释放锁后整个流程就结束了 释放读锁--后方有写锁等待 原子操作eaderCount 减1后发现eaderCount是小于0的此时说明已经有等待写锁的协程在尝试获取写锁。执行 rw.rUnlockSlow(r) 。func (rw *RWMutex) rUnlockSlow(r int32) {if r1 0 || r1 -rwmutexMaxReaders {race.Enable()throw(sync: RUnlock of unlocked RWMutex)}// A writer is pending.if atomic.AddInt32(rw.readerWait, -1) 0 {// The last reader unblocks the writer.runtime_Semrelease(rw.writerSem, false, 1)}
}这里是有个前提的上面提到详见上面的获取写锁的场景1如果写协程进来想加写锁需要把它需要等待的读锁数量从readerCount里赋值给readerWait。当它等待的读锁释放后就需要用rUnlockSlow方法对readerWait进行减1如果readWait 0 说明这是最后一个需要等待的读锁也释放了释放后就通知该写锁可以被唤醒了锁给你了。