然后重点来了,这里的这个操作细细体会一下,atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders)
是将当前的readerCount减去一个非常大的值rwmutexMaxReaders为1 << 30
大概是1073741823这么大吧
所以我们可以从源码中看出,readerCount由于每有一个协程获取读锁就+1,一直都是正数,而当有写锁过来的时候,就瞬间减为很大的负数。
然后做完上面的操作以后的r其实就是原来的readerCount。
后面进行判断,如果原来的readerCount不为0(原来有协程已经获取到了读锁)并且将readerWait加上readerCount(表示需要等待readerCount这么多个读锁进行解锁),如果满足上述条件证明原来有读锁,所以暂时没有办法获取到写锁,所以调用runtime_Semacquire进行等待,等待的信号量为writerSem
RUnlock(释放读锁) 
如果是我们来写的话,可能就是将之前+1的readerCount,-1就完事了,但是其实还有一些操作需要注意。
如果-1之后+1==0是啥情况?没错就是我们常见的,新手程序员,没有获取读锁就想去释放读锁,于是异常了。当然+1之后刚好是rwmutexMaxReaders,就证获取了写锁而去释放了读锁,导致异常。
除去异常情况,剩下的就是r还是<0的情况,那么证明确实有协程正在想要获取写锁,那么就需要操作我们前面看到的readerWait,当readerWait减到0的时候就证明没有人正在持有写锁了,就通过信号量writerSem的变化告知刚才等待的协程(想要获取写锁的协程):你可以进行获取了。
到这里你可以把思路大致串起来了,然后懂了再往下看。
Unlock(释放写锁) 
写锁释放需要恢复readerCount,还记得上锁的时候减了一个很大的数,这个时候要加回来了。
当然加完之后如果>=rwmutexMaxReaders本身,那么还是新手程序员的问题,当没有获取写锁的时候就开始想着释放写锁了。
然后for循环就是为了通知所有在我们RLock方法中看到的,当有因为持有写锁所以等待的那些协程,通过信号量readerSem告诉他们可以动了。
最后别忘记还有一个互斥锁需要释放,让别的协程也可以开始抢写锁了。
至此,读写锁的分析基本上告一段落了。
针对于其中关于竞态分析的代码,有兴趣的小伙伴可以去了解一下。
互斥锁Mutex
互斥锁比读写锁复杂,但是好在golang给的注释很详细,所以也不困难(注释真的很重要)。
我们先来看看里面的一段注释:
很长的一段英文,我用英语四级的翻译能力给你翻译一下,可以将就看看,如果可以建议你仔细看英文看懂它,因为这对于后面的源码阅读非常重要。
///
这个互斥锁是公平锁
互斥锁有两种操作模式:正常模式和饥饿模式。
在正常模式下等待获取锁的goroutine会以一个先进先出的方式进行排队,但是被唤醒的等待者并不能代表它已经拥有了这个mutex锁,它需要与新到达的goroutine争夺mutex锁。新来的goroutine有一个优势 —— 他们已经在CPU上运行了并且他们,所以抢到的可能性大一些,所以一个被唤醒的等待者有很大可能抢不过。在这样的情况下,被唤醒的等待者在队列的头部。如果一个等待者抢锁超过1ms失败了,就会切换为饥饿模式。
在饥饿模式下,mutex锁会直接由解锁的goroutine交给队列头部的等待者。
新来的goroutine不能尝试去获取锁,即使可能根本就没goroutine在持有锁,并且不能尝试自旋。取而代之的是他们只能排到队伍尾巴上乖乖等着。
如果一个等待者获取到了锁,并且遇到了下面两种情况之一,就恢复成正常工作模式。
情况1:它是最后一个队列中的等待者。
情况2:它等待的时间小于1ms
正常模式下,即使有很多阻塞的等待者,有更好的表现,因为一轮能多次获得锁的机会。饥饿模式是为了避免那些一直在队尾的倒霉蛋。
///
我的话简单总结就是,互斥锁有两种工作模式,竞争模式和队列模式,竞争就是大家一起抢,队列就是老老实实排队,这两种工作模式会通过一些情况进行切换。
首先还是来看看大体结构