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

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


可以看到,相对读写锁,结构上面很简单,只有两个值,但是千万不要小瞧它,减少了字段就增加了理解难度。
state:将一个32位整数拆分为:
当前阻塞的goroutine数(29位)
饥饿状态(1位,0为正常模式;1为饥饿模式)
唤醒状态(1位,0未唤醒;1已唤醒)
锁状态(1位,0可用;1占用)

sema:信号量

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


方法也很简单,就是Lock和Unlock两个方法,一个上锁,一个解锁,没啥好说的。

 

一个方法

我们先来看一个的要用到的方法

func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)
这个函数,会先判断参数addr指向的被操作值与参数old的值是否相等,如果相等会将参数new替换参数addr所指向的值,不然的话就啥也不做。
需要特别说明的是,这个方法并不会阻塞。

 

几个常量

这是定义的几个常量,我们在一开始的注释周围可以看到,后面需要用到,暂时记住它们的初始值就好。

mutexLocked = 1 << iota // 1左移0位,是1,二进制是1,(1表示已经上锁)
mutexWoken // 1左移1位,是2,二进制是10
mutexStarving // 1左移2位,是4,二进制是100
mutexWaiterShift = iota // 就是3, 二进制是11

starvationThresholdNs = 1e6 // 这个就是我们一开始在注释里面看到的1ms,一定超过这个门限值就会更换模式

 

Lock获取锁

因为Lock方法比较长,所以我切分一段段看,需要完整的请自己翻看源码。要注意的一点是,一定要时刻记住,Lock方法是做什么的,很简单,就是要抢锁。看不懂的时候想想这个目标。

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


第一步,判断state状态是否为0,如果为0,证明没有协程持有锁,那么就很简单了,直接获取到锁,将mutexLocked(为1)赋值到state就可以了。

看后面的方法时,告诉需要告诉你们一个小技巧,当遇到这种位操作很多的情况,有两个方法挺好用,对于你看源码会有帮助:
第一个是将所有定值先计算,然后判断非定值的情况;
第二个是将所有的计算写下来,自己用笔去计算,不要执着于打字。

然后我们以下面这个段举例:

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


首先,看注释应该能明白这一段大致意思是,如果不是饥饿模式,就会进行自旋操作,然后不断循环。

然后根据上面的技巧,old&(mutexLocked|mutexStarving) == mutexLocked
(下面均为二进制)
mutexLocked = 1
mutexStarving = 11
mutexLocked = 1
这三个是定值,所以我们容易得到,满足情况的结果为,当old为xxxx0xx(二进制第三位为0)等式成立。
也就是我们一开始说的,state的第三位是表示这个锁当前的模式,0为正常模式,1为饥饿模式。

那么第一个if就表示,如果当前模式为正常模式,且可以自旋,就进入if条件内部。


if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 &&

同样的分析,awoke表示是否唤醒,old&mutexWoken是取第二位,0表示当前协程未被唤醒,old>>mutexWaiterShift表示右移3位,也就是前29位,不为0证明有协程在等待,并且尝试去对比当前m.state与取出时的old状态,尝试去唤醒自己。然后自旋,并且增加自旋次数表示iter,然后重新赋值old。再循环下一次。

(你自己理一理,确实有点绕,仔细想想就想通了就对了。)

以上是使用自旋的情况,就是canSpin的。

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


然后进行判断old&mutexStarving == 0就是第三位为0的情况,还是所说的正常模式。new就马上拿到锁了,new |= mutexLocked,表示或1,就是第一位无论是啥都赋值为1

 

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

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