基于Mutex,实现读共享,写互斥 - RWMutex的底层原理和源码分析

背景

sync.Mutex提供了互斥锁,可以保证在同一时间段内,有且仅有一个goroutine持有锁和操作共享资源。其余goroutine只有在互斥锁被释放,成功获取到锁之后,才能操作共享资源

对共享资源的操作其实可以分为两种:

  • 读操作,不会改变共享资源
  • 写操作,会改变共享资源

在实际业务中,往往是读操作次数大于写操作次数,sync.Mutex提供的互斥锁,不能支持并发的读操作,所以就有了sync.RWMutex

sync.RWMutex有以下特点:

  • 在同一时间段,可以有多个goroutine获取到读锁,即读共享
  • 在同一时间段,只能有一个goroutine获取到写锁,即写互斥
  • 在同一时间段,只能存在读锁或写锁,即读写互斥

快速入门

读共享,写互斥快速入门:

go 复制代码
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中的锁接口:

go 复制代码
// A Locker represents an object that can be locked and unlocked.
type Locker interface {
   Lock()
   Unlock()
}
  • Locker表示可以锁定和解锁的对象

RWMutex实现了Locker接口中的方法,RWMutex结构和方法如下:

go 复制代码
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()

go 复制代码
// 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()

go 复制代码
// 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()

go 复制代码
// 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()

go 复制代码
// 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()

go 复制代码
// 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()

go 复制代码
// 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

go 复制代码
// 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具有写操作优先的特点,写操作发生时,只允许正在执行的读操作继续执行完成,后续新来的读操作都会被阻塞,直到写操作完成后进行唤醒
相关推荐
姑苏洛言1 分钟前
扫码小程序实现仓库进销存管理中遇到的问题 setStorageSync 存储大小限制错误解决方案
前端·后端
光而不耀@lgy16 分钟前
C++初登门槛
linux·开发语言·网络·c++·后端
方圆想当图灵35 分钟前
由 Mybatis 源码畅谈软件设计(七):SQL “染色” 拦截器实战
后端·mybatis·代码规范
毅航1 小时前
MyBatis 事务管理:一文掌握Mybatis事务管理核心逻辑
java·后端·mybatis
我的golang之路果然有问题1 小时前
速成GO访问sql,个人笔记
经验分享·笔记·后端·sql·golang·go·database
柏油1 小时前
MySql InnoDB 事务实现之 undo log 日志
数据库·后端·mysql
写bug写bug3 小时前
Java Streams 中的7个常见错误
java·后端
Luck小吕3 小时前
两天两夜!这个 GB28181 的坑让我差点卸载 VSCode
后端·网络协议
M1A14 小时前
全栈开发必备:Windows安装VS Code全流程
前端·后端·全栈
蜗牛快跑1234 小时前
github 源码阅读神器 deepwiki,自动生成源码架构图和知识库
前端·后端