基于Mutex,实现读共享,写互斥 - RWMutex的底层
背景
sync.Mutex提供了互斥锁,可以保证在同一时间段内,有且仅有一个goroutine持有锁和操作共享资源。其余goroutine只有在互斥锁被释放,成功获取到锁之后,才能操作共享资源
对共享资源的操作其实可以分为两种:
- 读操作,不会改变共享资源
- 写操作,会改变共享资源
在实际业务中,往往是读操作次数大于写操作次数,sync.Mutex提供的互斥锁,不能支持并发的读操作,所以就有了sync.RWMutex
sync.RWMutex有以下特点:
- 在同一时间段,可以有多个goroutine获取到读锁,即读共享
- 在同一时间段,只能有一个goroutine获取到写锁,即写互斥
- 在同一时间段,只能存在读锁或写锁,即读写互斥
快速入门
读共享,写互斥快速入门:
func TestRWMutexLock(t *testing.T) {
var rw sync.RWMutex
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
go func() {
wg.Add(1)
defer wg.Done()
// 读锁
rw.RLock()
defer rw.RUnlock()
time.Sleep(1 * time.Second)
fmt.Println("读操作共享")
}()
}
for i := 0; i < 10; i++ {
go func() {
wg.Add(1)
defer wg.Done()
// 写锁
rw.Lock()
defer rw.Unlock()
time.Sleep(1 * time.Second)
fmt.Println("写操作互斥")
}()
}
wg.Wait()
}
源码分析
源码路径:src/sync/rwmutex.go
golang中的锁接口:
// A Locker represents an object that can be locked and unlocked.
type Locker interface {
Lock()
Unlock()
}
- Locker表示可以锁定和解锁的对象
RWMutex实现了Locker接口中的方法,RWMutex结构和方法如下:
const rwmutexMaxReaders = 1 << 30
// There is a modified copy of this file in runtime/rwmutex.go.
// If you make any changes here, see if you should make them there.
// A RWMutex is a reader/writer mutual exclusion lock.
// The lock can be held by an arbitrary number of readers or a single writer.
// The zero value for a RWMutex is an unlocked mutex.
//
// A RWMutex must not be copied after first use.
//
// If a goroutine holds a RWMutex for reading and another goroutine might
// call Lock, no goroutine should expect to be able to acquire a read lock
// until the initial read lock is released. In particular, this prohibits
// recursive read locking. This is to ensure that the lock eventually becomes
// available; a blocked Lock call excludes new readers from acquiring the
// lock.
type RWMutex struct {
w Mutex // held if there are pending writers
writerSem uint32 // semaphore for writers to wait for completing readers
readerSem uint32 // semaphore for readers to wait for completing writers
readerCount int32 // number of pending readers
readerWait int32 // number of departing readers
}
// RWMutex提供了以下几个核心方法
// 加读锁,没有成功时,会一直阻塞
func (rw *RWMutex) RLock() {}
// 尝试加读锁,没有成功时,会快速返回结果,不阻塞
func (rw *RWMutex) TryRLock() bool {}
// 解读锁
func (rw *RWMutex) RUnlock() {}
// 加写锁,没有成功时,会一直阻塞
func (rw *RWMutex) Lock() {}
// 尝试加写锁,没有成功时,会快速返回结果,不阻塞
func (rw *RWMutex) TryLock() bool {}
// 解写锁
func (rw *RWMutex) Unlock() {}
// 返回Locker接口的实现
func (rw *RWMutex) RLocker() Locker {}
- rwmutexMaxReaders:表示RWMutex能接受的最大读操作数量,超过最大数量就会panic
- w:互斥锁,用于实现互斥写操作
- writerSem:写操作信号量,用于写操作的阻塞和唤醒。当存在正在执行的读操作时,写操作会被阻塞;当读操作全部完成后,通过writerSem写操作信号量来唤醒写操作
- readerSem:读操作信号量,用于读操作的阻塞和唤醒。当存在正在执行的写操作时,读操作会被阻塞;当写操作完成后,通过readerSem读操作信号量唤醒读操作
- readerCount:正在执行的读操作数量,当不存在写操作时,从0开始计数,通过正数来表示;当存在写操作时,从负的rwmutexMaxReaders开始计数,通过负数来表示
- readerWait:写操作等待读操作的数量,当执行Lock方法时,如果当前存在正在执行的读操作,会将正在执行的读操作数量记录在readerWait中,并阻塞写操作;当读操作执行完成后,会更新readerWait;当readerWait为0时,会唤醒写操作
- RWMutex具有写操作优先的特点,写操作发生时,只允许正在执行的读操作继续执行完成,后续新来的读操作都会被阻塞,直到写操作完成后进行唤醒
Lock()
// Lock locks rw for writing.
// If the lock is already locked for reading or writing,
// Lock blocks until the lock is available.
func (rw *RWMutex) Lock() {
if race.Enabled {
_ = rw.w.state
race.Disable()
}
// First, resolve competition with other writers.
// 加写锁,保证写操作互斥
rw.w.Lock()
// Announce to readers there is a pending writer.
// 将readerCount更新为负值,表示当前有写操作
// 当readerCount为负数时,新的读操作会被阻塞
// r表示当前正在执行的读操作数量
r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders
// Wait for active readers.
// r != 0 表示当前存在正在执行的读操作
// 把当前正在执行的读操作数量更新到readerWait中
if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 {
// 阻塞写操作,等待正在执行的读操作执行完后唤醒,执行RUnlock()
runtime_SemacquireMutex(&rw.writerSem, false, 0)
}
if race.Enabled {
race.Enable()
race.Acquire(unsafe.Pointer(&rw.readerSem))
race.Acquire(unsafe.Pointer(&rw.writerSem))
}
}
先通过Mutex进行加锁,保证写操作互斥
将readerCount更新为负值,表示当前有写操作。当readerCount为负数时,新的读操作调用RLock()尝试加读锁时会被阻塞,保证写操作优先
若当前存在正在执行的读操作,把当前正在执行的读操作数量更新到readerWait中
阻塞当前写操作,读操作执行完调用RUnlock(),如果存在写操作,会对readerWait进行-1,当readerWait==0时,唤醒写操作,保证写操作优先
Unlock()
// Unlock unlocks rw for writing. It is a run-time error if rw is
// not locked for writing on entry to Unlock.
//
// As with Mutexes, a locked RWMutex is not associated with a particular
// goroutine. One goroutine may RLock (Lock) a RWMutex and then
// arrange for another goroutine to RUnlock (Unlock) it.
func (rw *RWMutex) Unlock() {
if race.Enabled {
_ = rw.w.state
race.Release(unsafe.Pointer(&rw.readerSem))
race.Disable()
}
// Announce to readers there is no active writer.
// 将readerCount更新为正数,表示当前没有写操作
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()
}
}
将readerCount更新为正数,表示当前没有写操作
若存在等待的读操作,则唤醒所有等待的读操作
释放互斥锁
TryLock()
// TryLock tries to lock rw for writing and reports whether it succeeded.
//
// Note that while correct uses of TryLock do exist, they are rare,
// and use of TryLock is often a sign of a deeper problem
// in a particular use of mutexes.
func (rw *RWMutex) TryLock() bool {
if race.Enabled {
_ = rw.w.state
race.Disable()
}
// 尝试加写锁
if !rw.w.TryLock() {
if race.Enabled {
race.Enable()
}
return false
}
// 加写锁成功
// 通过CAS,判断是否存在读操作,如果不存在读操作,则加写锁成功;如果存在读操作,则释放写锁
if !atomic.CompareAndSwapInt32(&rw.readerCount, 0, -rwmutexMaxReaders) {
rw.w.Unlock()
if race.Enabled {
race.Enable()
}
return false
}
if race.Enabled {
race.Enable()
race.Acquire(unsafe.Pointer(&rw.readerSem))
race.Acquire(unsafe.Pointer(&rw.writerSem))
}
return true
}
尝试加写锁,如果加写锁失败,则直接返回false
如果加写锁成功,通过CAS,判断是否存在读操作,如果不存在读操作,则加写锁成功,返回true;如果存在读操作,则释放写锁,返回false
RLock()
// Happens-before relationships are indicated to the race detector via:
// - Unlock -> Lock: readerSem
// - Unlock -> RLock: readerSem
// - RUnlock -> Lock: writerSem
//
// The methods below temporarily disable handling of race synchronization
// events in order to provide the more precise model above to the race
// detector.
//
// For example, atomic.AddInt32 in RLock should not appear to provide
// acquire-release semantics, which would incorrectly synchronize racing
// readers, thus potentially missing races.
// RLock locks rw for reading.
//
// It should not be used for recursive read locking; a blocked Lock
// call excludes new readers from acquiring the lock. See the
// documentation on the RWMutex type.
func (rw *RWMutex) RLock() {
if race.Enabled {
_ = rw.w.state
race.Disable()
}
// 原子更新readerCount+1,表示读操作数量+1
// 若readerCount+1为负数,表示当前存在写操作,读操作会被阻塞,等待写操作完成后被唤醒
if atomic.AddInt32(&rw.readerCount, 1) < 0 {
// A writer is pending, wait for it.
// 阻塞读操作,执行Unlock(),唤醒所有阻塞等待的读操作,释放写锁
runtime_SemacquireMutex(&rw.readerSem, false, 0)
}
if race.Enabled {
race.Enable()
race.Acquire(unsafe.Pointer(&rw.readerSem))
}
}
原子更新readerCount+1,读操作数量+1
如果readerCount+1为负数,则表示当前存在写操作,此时需要加锁的读操作会被阻塞,保证写操作优先;等待写操作完成,执行Unlock()再唤醒读操作
RUnlock()
// RUnlock undoes a single RLock call;
// it does not affect other simultaneous readers.
// It is a run-time error if rw is not locked for reading
// on entry to RUnlock.
func (rw *RWMutex) RUnlock() {
if race.Enabled {
_ = rw.w.state
race.ReleaseMerge(unsafe.Pointer(&rw.writerSem))
race.Disable()
}
// 原子更新readerCount-1,表示读操作数量-1
// 若readerCount-1为负数,表示当前读操作阻塞了写操作,需要进行额外处理
if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 {
// Outlined slow-path to allow the fast-path to be inlined
rw.rUnlockSlow(r)
}
if race.Enabled {
race.Enable()
}
}
func (rw *RWMutex) rUnlockSlow(r int32) {
// 判断是否超过最大读操作数量
if r+1 == 0 || r+1 == -rwmutexMaxReaders {
race.Enable()
throw("sync: RUnlock of unlocked RWMutex")
}
// A writer is pending.
// 原子更新readerWait-1,表示阻塞写操作的读操作数量-1
// 当readerWait-1为0时,表示导致写操作阻塞的所有读操作都已经执行完成,此时需要把阻塞的写操作唤醒
if atomic.AddInt32(&rw.readerWait, -1) == 0 {
// The last reader unblocks the writer.
// 唤醒写操作
runtime_Semrelease(&rw.writerSem, false, 1)
}
}
原子更新readerCount-1,表示读操作数量-1
若readerCount-1为负数,表示当前读操作阻塞了写操作,需要进行额外处理
原子更新readerWait-1,表示阻塞写操作的读操作数量-1
当readerWait-1为0时,表示导致写操作阻塞的所有读操作都已经执行完成,此时需要把阻塞的写操作唤醒
TryRLock()
// TryRLock tries to lock rw for reading and reports whether it succeeded.
//
// Note that while correct uses of TryRLock do exist, they are rare,
// and use of TryRLock is often a sign of a deeper problem
// in a particular use of mutexes.
func (rw *RWMutex) TryRLock() bool {
if race.Enabled {
_ = rw.w.state
race.Disable()
}
for {
c := atomic.LoadInt32(&rw.readerCount)
// readerCount为负数,表示存在写操作,加读锁失败
if c < 0 {
if race.Enabled {
race.Enable()
}
return false
}
// 尝试加读锁
if atomic.CompareAndSwapInt32(&rw.readerCount, c, c+1) {
if race.Enabled {
race.Enable()
race.Acquire(unsafe.Pointer(&rw.readerSem))
}
return true
}
}
}
readerCount为负数,表示存在写操作,加读锁失败
尝试加读锁,成功则返回,失败则继续下一次循环尝试
RLocker
// RLocker returns a Locker interface that implements
// the Lock and Unlock methods by calling rw.RLock and rw.RUnlock.
func (rw *RWMutex) RLocker() Locker {
return (*rlocker)(rw)
}
type rlocker RWMutex
func (r *rlocker) Lock() { (*RWMutex)(r).RLock() }
func (r *rlocker) Unlock() { (*RWMutex)(r).RUnlock() }
RWMutex的Lock()和Unlock(),默认是:
- Lock():加写锁
- Unlock():释放写锁
如果希望是加读锁和释放读锁,可以使用RLocker()返回的Locker实现:
- Lock():底层调用RLock(),加读锁
- Unlock():底层调用RUnlock(),释放读锁
总结
- RWMutex具有读共享,写互斥的特点,底层依赖于Mutex来实现
- RWMutex具有写操作优先的特点,写操作发生时,只允许正在执行的读操作继续执行完成,后续新来的读操作都会被阻塞,直到写操作完成后进行唤醒