当前位置:首页 > 技术知识 > 正文内容

go语言并发原语RWMutex实现原理及闭坑指南

maynowei10个月前 (08-03)技术知识260

1.RWMutex常用方法

  • Lock/Unlock
  • RLock/RUnlock
  • RLocker 为读操作返回一个Locker接 口的对象

2. RWMutex使用方法

 func main() {
 var counter Counter
 for i := 0; i < 10; i++ { // 10个reader
 go func() {
 for {
 counter.Count() // 计数器读操作
 time.Sleep(time.Millisecond)
 }
 }()
 }
 for { // 一个writer
 counter.Incr() // 计数器写操作
 time.Sleep(time.Second)
 }
 }
 // 一个线程安全的计数器
 type Counter struct {
 mu sync.RWMutex
 count uint64
 }
 // 使用写锁保护
 func (c *Counter) Incr() {
 c.mu.Lock()
 c.count++
 c.mu.Unlock()
 }
 / 使用读锁保护
 func (c *Counter) Count() uint64 {
 c.mu.RLock()
 defer c.mu.RUnlock()
 return c.count
 }

在读多写少的场景下,使用RWMutex性能要更好些

3.实现原理

RWMutex是基于Mutex实现的,以写优先来实现的

 type RWMutex struct {
     w Mutex // 互斥锁解决多个writer的竞争
     writerSem uint32 // writer信号量
     readerSem uint32 // reader信号量
     readerCount int32 // reader的数量
     readerWait int32 // writer等待完成的reader的数量(记录写锁来的时候前面还需要等待多少个读锁,其实也是在某一时刻 readerCount 的复制值)
 }
 const rwmutexMaxReaders = 1 << 30

说明:

  • 这里的常量 rwmutexMaxReaders,定义了最大的 reader 数量。
  • 字段 w:为 writer 的竞争锁而设计;
  • 字段 readerCount:记录当前 reader 的数量(以及是否有 writer 竞争锁);
  • readerWait:记录 writer 请求锁时需要等待 read 完成的 reader 的数量;
  • writerSem 和 readerSem:都是为了阻塞设计的信号量。

3.1 RLock/RUnlock的实现

 func (rw *RWMutex) RLock() {
     
      // 每次goroutine获得读锁,readerCount+1
     // 这里分两种情况:
     // 1. 当判断大于等于0, 证明当前没有写锁, 那么可以上读锁, 并且readerCount原子加1(读锁可重入[只要匹配了释放次数就行])
     // 2. 当判断小于0, 证明当前有写锁(Lock时会readerCount-rwmutexMaxReaders, 因此会小于0), 所以通过readerSem读信号量, 使读操作睡眠等待
 if atomic.AddInt32(&rw.readerCount, 1) < 0 {
 // rw.readerCount是负值的时候,意味着此时有writer等待请求锁,因为writer优先
     runtime_SemacquireMutex(&rw.readerSem, false, 0)
     }
 }
 func (rw *RWMutex) RUnlock() {
     
     // 这里分两种情况:
     // 释放读锁, readerCount减1
     // 1.若readerCount大于0, 证明当前还有读锁, 直接结束本次操作
     // 2.若readerCount小于等于0, 证明已经没有读锁, 可以唤醒写锁(若有)
 if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 {
     rw.rUnlockSlow(r) // 有等待的writer
     }
 }
 func (rw *RWMutex) rUnlockSlow(r int32) {
     // 1. 本来就没读锁, 调用RUnlock就发生panic
     // 2. 超过读锁的最大限制就发生panic
     if r+1 == 0 || r+1 == -rwmutexMaxReaders {
         race.Enable()
         throw("sync: RUnlock of unlocked RWMutex")
     }
     // readerWait--操作,如果readerWait--操作之后的值为0,说明,写锁之前,已经没有读锁了
     // 通过writerSem信号量,唤醒队列中第一个阻塞的写锁
 if atomic.AddInt32(&rw.readerWait, -1) == 0 {
 // 最后一个reader了,writer终于有机会获得锁了
 runtime_Semrelease(&rw.writerSem, false, 1)
 }

3.2 Lock的实现

 func (rw *RWMutex) Lock() {
 // 首先解决其他writer竞争问题
 rw.w.Lock()
  //// 先readerCount-rwmutexMaxReaders<0,标识有写锁来了,让后续来的读锁堵塞无法拿到锁
     // 再加回rwmutexMaxReaders得到当前读锁的个数
 // 反转readerCount,告诉reader有writer竞争锁
 r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxRead
 // 如果当前有reader持有锁,那么需要等待
 //// 读锁个数不为0的时候,写锁阻塞,把当前的读锁个数readerCount赋值给readerWait
     // 用于标识写锁前面还需要等待多少个读锁
 if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 {
 runtime_SemacquireMutex(&rw.writerSem, false, 0)
 }
 }

3.3 Unlock的实现

 func (rw *RWMutex) Unlock() {
 // 告诉reader没有活跃的writer了
 r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders)
 // 唤醒阻塞的reader们
 for i := 0; i < int(r); i++ {
 runtime
 _
 Semrelease(&rw.readerSem, false, 0)
 }
 // 释放内部的互斥锁
 rw.w.Unlock()
 }

4 使用RWMutex的注意点

  • 不可复制
  • 重入导致死锁
  • 释放未加锁的RWMutex,会产生panic

相关文章

分享我的产品策划流程,希望对你也有用

本文笔者梳理拆解了自己的产品策划流程,并给出了自己对各流程的思考,希望能够给你带来一定的启发。记得刚开始做产品出需求方案的时候,上来就开始画原型写文档,在写的过程中发现某个交互没想明白或者漏了一部分逻...

2018年度回顾:挖矿木马为什么会成为病毒木马黑产的中坚力量

一、概述根据腾讯御见威胁情报中心监测数据,2018年挖矿木马样本月产生数量在百万级别,且上半年呈现快速增长趋势,下半年上涨趋势有所减缓。由于挖矿的收益可以通过数字加密货币系统结算,使黑色产业变现链条十...

CPU「离奇」飙到 100%!开发者挖出 Linux 内核 16 年老 Bug:这么多年竟无人发现?

【CSDN 编者按】每一次对旧设备的升级都仿佛是一场跨越时代的冒险。本文作者致力于将基于 PXA166 的 Chumby 8 设备从 Linux 2.6.28 版本升级到现代 6.x 版本,然而,在看...

C语言进阶教程:线程同步:互斥锁、条件变量与信号量

在多线程编程中,线程同步是确保数据一致性和程序正确性的关键。当多个线程需要访问共享资源时,如果缺乏适当的同步机制,就可能导致竞态条件(Race Condition)、死锁(Deadlock)等问题。本...

关于异步信号安全(下面关于异步电路危害的描述错误的是)

线程安全与重入以及异步信号安全的区别. 可重入一定是线程安全的,但是线程安全不一定是可重入的. 引用别人的博客中的话吧.如下: http://blog.csdn.net/xiaofei0859/art...

C语言编写多线程,什么时候要使用互斥锁?为什么要使用互斥锁?

在多线程编程中,当多个线程同时访问共享资源(如变量、文件等)时,会出现竞态条件(Race Condition)问题,导致程序的行为不可预测。为了避免这种问题,需要使用互斥锁来保护共享资源的访问。互斥锁...