golang源代码阅读,sync系列-读写互斥锁RWMutex

总结

  1. 实现的原理相对Mutex较简单,使用Mutex、信号量和atomic实现
  2. 写锁之间的互斥通过Mutex互斥锁实现
  3. 写锁和读锁之间的互斥通过信号量和原子操作实现,巧妙将readerCount减去一个大值1<30,然后配合上信号量实现读锁和写锁间的互斥

对比Mutex

读写锁相对与互斥锁,读写锁在读上锁的时候,大部分场景只需要通过atomic进行加操作即可,不需要其它复杂操作

关键字

Mutex、信号量、atomic实现

场景

多个写锁

  1. 多个写锁进行上锁,通过Mutex互斥锁实现互斥,保证当前只有一个写锁,进入上锁状态

写锁上锁后,进行写锁

  1. 因为写锁上锁后,会通过原子加操作将readerCount加上一个负值-1<30
  2. 后续读锁上锁时,通过原子加操作将readerCount加1,此时仍小于0,会去申请readerSem信号量,这时readerSem等于0,因此当前goroutine会被阻塞。
  3. 当前写锁进行Unlock时,通过原子加操作,将readerCount加上1<30,这时会获取到等待读锁加锁的个数,然后通过释放readerSem信号量,
    这时读锁就能进行加锁成功了

多个读锁成功后,进行写锁加锁

  1. 通过原子加操作将readerCount加上1,几个写锁加锁,就会加上几
  2. 写锁加锁,因为readerCount>0,进行r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders,r会大于0
  3. 将readerCount原子加操作加上r后,大于0,会去申请writerSem信号量,因为等于0,因此当写锁会被阻塞
  4. 读锁解锁时,会将readerWait通过原子加操作加上-1,通过判断readerWait是否等于0判读是否为最后一个读锁解锁,最后一个读锁解锁,会释放一个writerSem信号量,从而写锁能够获取到信号量,加锁成功。

代码注释说明

代码结构

sync/rwmutex.go里面实现

使用方式

主要包括四个函数
写锁操作
Lock:写操作上锁,如果有写锁存在,其它操作被阻塞
UnLock:写操作解锁
读锁操作
RLock:读操作上锁,如果读锁存在,写锁阻塞,读锁支持多个goroutine加锁,各个读锁之间不阻塞
RUnlock:读操作解锁

代码走读

结构体说明

  1. mutex/rwmutex.go是runtime/rwmutex.go里修改后的副本
  2. 多个读锁间并行,写锁间互斥,读锁和写锁间互斥。RWMutex的零值是未上锁状态
  3. RWMutex初始化后,不能被复制拷贝
  4. 读锁禁止递归调用,防止最终能获取到锁,出现死锁
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 当前读锁的个数,用作实现读锁和写锁之间的互斥
}

写锁

  1. 写锁之间是互斥的
  2. 读锁与写锁之间互斥
上锁
func (rw *RWMutex) Lock() {
    // First, resolve competition with other writers.
    // 实现写锁唯一,其它goroutine再进行写锁,或被阻塞
    rw.w.Lock()
    // Announce to readers there is a pending writer.
    // 通过atomic操作将readerCount赋值为负值,rwmutexMaxReaders大小为1<30,
    // 初始值readerCount大小为0,此次操作后赋值为-1<30,r能为0
    r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders
    // Wait for active readers.
    // 如果r不为0,则说明readerCount大于0,即当前有读锁已经进入了上锁状态
    // 如果readerWait+r不为0,则表明有读锁存在,则进行获取写锁信号量,因为writerSem默认值为0,被写锁被阻塞
    // 后面对readerWait的原子加操作,是记录当前读锁上锁个数,
    // 用做RUnlock里面释放读锁时,最后一个读锁释放,释放一个writerSem信号量
    if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 {
        runtime_SemacquireMutex(&rw.writerSem, false, 0)
    }
}
解锁
  1. 写锁解锁
  2. 如果没有进行写上锁,直接写解锁,会抛异常
  3. 写上锁和写解锁的goroutine可能不一样
func (rw *RWMutex) Unlock() {
    // Announce to readers there is no active writer.
    // 通过原子操作将readerCount加上1<30,
    r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders)
    if r >= rwmutexMaxReaders {
        race.Enable()
        throw("sync: Unlock of unlocked RWMutex")
    }
    // Unblock blocked readers, if any.
    // 解锁阻塞的读锁的goroutines,通过释放readerSem信号量,r为几,则有几个reader
    // 下面几行代码的实现的场景是:写锁已经上锁,这时有其它读锁进来上锁,因为  
    // 写锁存在,读锁上锁都会被阻塞,因此释放写的时候,会释放当前被阻塞
    // 的读锁的个数的信号量,这样读锁就能获取到信号量,进行上锁了
    for i := 0; i < int(r); i++ {
        runtime_Semrelease(&rw.readerSem, false, 0)
    }
    // Allow other writers to proceed.
    // 解锁写锁的大锁w,这样其它的goroutine就能进行写上锁了
    rw.w.Unlock()
}

读锁

读锁之间是非互斥的,可以多个goroutine进行读锁操作,每个都能获取到,与写锁之间是互斥的

  1. 写锁存在,写锁上锁会被阻塞
上锁
func (rw *RWMutex) RLock() {
    // 将readerCount加1,如果小于0,则说明已经有写锁进行了加锁,写锁会通过原子加操作将readerCount设置负值
    // 小于0,则通过获取信号量readerSem,因为readerSem默认为值为0,则会
    if atomic.AddInt32(&rw.readerCount, 1) < 0 {
        // A writer is pending, wait for it.
        // 获取readerSem信号量,只有写锁已经存在,才会进入这里,读锁存在,readerSem为0,当前goroutine会被阻塞
        runtime_SemacquireMutex(&rw.readerSem, false, 0)
    }
}
解锁
func (rw *RWMutex) RUnlock() {
    // 通过原子加操作,将readerCount减一,如果小于0,则表明当前已经写锁加锁
    if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 {
        // Outlined slow-path to allow the fast-path to be inlined
        rw.rUnlockSlow(r)
    }
}
func (rw *RWMutex) rUnlockSlow(r int32) {
    // 未进行RLock,直接进行RUnlock会抛异常
    if r+1 == 0 || r+1 == -rwmutexMaxReaders {
        race.Enable()
        throw("sync: RUnlock of unlocked RWMutex")
    }
    // A writer is pending.
    // readerWait只有会在两个地方进行赋值,在写上锁时
    // 释放最有一个读锁时,释放一个writerSem信号量,然后写锁可以获取到信号量,加锁成功。
    if atomic.AddInt32(&rw.readerWait, -1) == 0 {
        // The last reader unblocks the writer.
        runtime_Semrelease(&rw.writerSem, false, 1)
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值