Golang 读写锁RWMutex 互斥锁Mutex 源码详解 (4)

old&(mutexLocked|mutexStarving),也就是old & 0101
必须当old的1和3两个位置为1的时候才是true,也就是说当前处于饥饿模式,并且锁已经被占用的情况,那么就需要排队去。
排队也很精妙,new += 1 << mutexWaiterShift
这边注意是先计算1 << mutexWaiterShift也就是将new的前29位+1,就是表示有一个协程在等待了。

 

好了到这里你的位操作应该就习惯的差不多了,之后我就直接说结论,不仔细的帮你01表示了,你已经长大了,要学会自己动手了。

如果当前已经标记为饥饿模式,并且没有锁住,那么设置new为饥饿模式
if starving && old&mutexLocked != 0 {
new |= mutexStarving
}

 

如果唤醒,需要在两种情况下重设标志
if awoke {
如果唤醒标志为与awoke不相协调就panic
if new&mutexWoken == 0 {
throw("sync: inconsistent mutex state")
}
设置唤醒状态位0,被唤醒
new &= mutexWoken
}

Golang 读写锁RWMutex 互斥锁Mutex 源码详解


如果获取锁成功

old&(mutexLocked|mutexStarving) == 0成立表示已经获取锁,就直接退出CAS

中间这一段我就不多解释了,就是最前面注释说的,满足什么条件转换什么模式,不多说了。然后从队列中,也就是前29位-1。
需要注意其中有一个runtime_SemacquireMutex和之前看的的runtime_Semacquire是一个意思,只是多了一个参数。

Golang 读写锁RWMutex 互斥锁Mutex 源码详解


这个就是这个方法的注释。可以看到,就是多了个队列去排队。

Golang 读写锁RWMutex 互斥锁Mutex 源码详解


如果获取锁失败,old刷新状态再次循环,继续cas

 

UnLock释放锁

Golang 读写锁RWMutex 互斥锁Mutex 源码详解

Unlock就相对简单一些,竞态分析不看。
其实我们自己想也能想到,unlock就是将标识位改回来嘛。
然后因为我们已经看过读写锁了,也是同样的道理,如果没有上锁就直接解锁,那肯定报错嘛。

Golang 读写锁RWMutex 互斥锁Mutex 源码详解


然后如果是正常模式,如果没有等待的goroutine或goroutine已经解锁完成的情况就直接返回了。如果有等待的goroutine那就通过信号量去唤醒runtime_Semrelease(注意这里是false),同时操作一下队列-1

Golang 读写锁RWMutex 互斥锁Mutex 源码详解


如果是饥饿模式就直接唤醒(注意这里是true),反正有队列嘛。

 

总结

其实话说回来,我们其实看起来也简单,没有冲突的情况下,能拿就拿呗,如果出现冲突了就尝试自旋解决(自旋一般都能解决)如果解决不了就通过信号量解决,同时如果正常模式就是我们说的抢占式,非公平,如果是饥饿模式,就是我们说的排队,公平,防止有一些倒霉蛋一直抢不到。

整体总结一下,看完源码我们发现,其实锁的设计并不复杂,主要设计我们要学到cas和处理读写状态的信号量通知,对于那些位操作,能看懂,学可能一时半会学不会,因为很难在一开始就设计的那么巧妙,你也体会到了只用一个变量就维护了整个体系是一种艺术。

 写的着急,难免有疏漏,如果有任何问题请评论,马上修改,以免误导。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpgppd.html