本文代码基于go 1.22, 其他版本大差不差
前言
上篇博客分析了sync.Mutex
的源码,我们在碰到共享资源问题的时候可以使用sync.Mutex
来解决。
但是在实际的开发中,我们会遇到一种情况,就是读多写少的场景。大部分请求都是读请求,并不涉及到数据的修改,这个时候如果还使用sync.Mutex
的话,会导致大量的读请求被阻塞(没有意义)。这类情况或者说问题,被称为readers-writers
问题。
此时就需要本文的主角,也就是sync.RWMutex
来解决这个问题。
RWMutex
RWMutex
也称为读写锁。它的特点是:
可以有多个协程同时读取共享资源,当有读操作进行时,后续的写操作阻塞,等待当前操作完成。
同一时刻只能有一个协程写共享资源,写操作进行时,后续进来的读操作阻塞。
想像一下一个有“画面感”的场景:
你和朋友一起去厕所,但是你们并不着急,只是想看看坑位构造(两个读协程)。
厕所只有一个坑位(共享资源)。此时坑位没人占用,于是你们两个可以同时进去看看。但是如果你们看之前坑位有人在用(写协程),那么你们就得等待,等ta上完厕所才可以看(读协程被阻塞),毕竟不等直接看的话不太好。
上面这个例子可以知道读写锁的特点,接下看看sync.RWMutex
的结构:
|
|
字段的含义都写在代码里了,接下来看它提供的方法:
|
|
这里省略了TryLock
与TryRLock
方法,它们不是本文重点。
RLock与RUnLock
调用RLock
方法可以获取读锁,RUnlock
方法用于释放读锁。如何使用不是本文重点,接下来直接看实现(简化版):
|
|
readerCount
加1,很好理解,因为读协程进来了。- 判断加一后的值是否小于0。这有疑问,大家先记着答案:如果小于0,说明此时有写协程在等待。有写协程在等待,那么将当前的读协程阻塞(通过调用
runtime_SemacquireRWMutexR(&rw.readerSem, false, 0)
)。
RUnlock
方法的实现:
|
|
readerCount
减1,很好理解,因为读协程走了。- 判断减1后的值是否小于0,如果小于0,说明有写协程在等待。此时通过内联的方式调用
rUnlockSlow
方法。 rUnlockSlow
方法中,先判断是否解锁一个未加锁的读协程。通过判断原本的readerCount
值是否为0,和是否为-rwmutexMaxReaders
。前者好理解,后者后续会说明。如果原本未加锁,那么调用fatal
方法报错。- 如果
readerWait
值为0,说明此时没有读协程在操作了,唤醒写协程。
通过阅读RLock
与RUnlock
实现。我们可以知道readerCount
不仅像字面意思一样记录读协程的数量,还有一个隐藏的作用:判断是否有写协程在操作。它作为一个复合型字段(一个字段表达多种含义),类似于sync.Mutex
中的state
字段。
Lock与UnLock
Lock
方法:
|
|
- 先通过
RWMutex
内嵌的Mutex
字段w
获取写锁,目的是确保只有一个写协程在操作。 - 能走到这里,说明写协程获取到锁,正在操作了。此时它先将
readerCount
减去rwmutexMaxReaders
。这样做的目的是:让readerCount
的值小于0,表示有写协程在操作。这样别的读协程进来时,看到小于0就不会再去获取读锁了。 - 变量
r
代表了当前读协程的数量。如果r
不为0,说明有读协程在操作,此时将readerWait
加上r
,同时阻塞当前写协程。这样做的目的是:让写协程等待所有读协程操作完成,它再去做写操作。
UnLock
方法:
|
|
- 刚刚对
readerCount
减去rwmutexMaxReaders
,现在再加回来。目的是表示当前写操作完成了。 - 判断
readerCount
是否大于等于rwmutexMaxReaders
,如果大于等于,说明解锁一个未加锁的写协程,报错。 - 唤醒当前所有等待的读协程,释放信号量,让它们继续读操作。
- 最后释放写锁。
本质上写锁是通过sync.Mutex
来控制多个写锁间的并发问题。而通过readerCount
是否大于0来控制读协程与写协程的并发问题。
总结
读写锁的实现还是比较简单的(相比于sync.Mutex
)。
读操作进来时先判断是否有写操作,有的话就阻塞当前协程。反过来写操作进来时,先获取sync.Mutex
锁,再反转readerCount
的值,让后续读操作阻塞。再判断当前有读操作的话,阻塞写操作。
虽然经常说golang的并发控制:
不要通过共享内存来通信,而应该通过通信来共享内存。
但平时还是基于锁来操作共享资源的。而使用锁的话,我们要根据实际场景选择合适的锁。sync.RWMutex
就是为了解决读多写少(readers-writers)的场景。