我们在多协程操作时,有种场景是读操作次数远远大于写操作,这个时候,我们就会考虑用到读写锁。

读写锁

读写锁(百科)定义:是一种特殊的的自旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。

在Go语言中、sync标准库定义了RWMutex结构代表读写锁,该结构在rwmutex.go中,RWMutex继承于Locker接口,其实是互斥锁的改进版,为什么这样说呢,看完下文就会清楚了。

读写锁特征:

读锁之间是共享的,多协程可以同时加读锁,写锁与读锁之间是互斥的。

源码基于:go version go1.13.4 windows/amd64。

Locker接口

type Locker interface {Lock()Unlock()
}

读写锁结构体定义

type RWMutex struct {w           Mutex  // 互斥锁,写锁协程获取该锁后,其他写锁处于阻塞等待writerSem   uint32 // 写入等待信号量,最后一个读取协程释放锁时会释放信号量readerSem   uint32 // 读取等待信号量,持有写锁协程释放后会释放信号量readerCount int32  // 读锁的个数readerWait  int32  // 写操作时,需要等待读操作的个数
}

我们看到RWMutex读写锁里面包含:

  1. w Mutex互斥锁,w控制并发时多个写操作
  2. writerSem 写入等待信号量
  3. readerSem 读取等待信号量
  4. readerCount 读锁的个数
  5. readerWait 写操作时,需要等待读操作的个数

再看常量rwmutexMaxReaders,定义的是最大读锁数量

// rwmutexMaxReaders:支持1073741824个锁
const rwmutexMaxReaders = 1 << 30
sync.RWMutex所有方法如下图所示

读写锁的核心方法:其主要核心的有Lock、Unlock、RLock和RUnlock四个方法。

func (*RWMutex) Lock    // 获取写锁操作
func (*RWMutex) Unlock  // 解除写锁操作
func (*RWMutex) RLock   // 获取读锁操作
func (*RWMutex) RUnlock // 解除读锁操作
RLocker() Locker        // 返回Locker对象

RLocker,返回一个Locker对象

// 返回一个Locker对象
func (rw *RWMutex) RLocker() Locker {return (*rlocker)(rw)
}

Lock,获取写锁操作

func (rw *RWMutex) Lock() {// ①、竞争锁时检测if race.Enabled {_ = rw.w.staterace.Disable()}// 先获取Mutex互斥锁,多协程时只能有一个协程拿到锁rw.w.Lock()// ②、写锁时,会将readerCount减去rwmutexMaxReaders,// 设置需要等待释放的读锁的数量,如有则挂起获取读锁的协程r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReadersif r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 {// ③如果先进来的,会在队列前面阻塞等待,进入队列等待runtime_SemacquireMutex(&rw.writerSem, false, 0)}if race.Enabled {race.Enable()race.Acquire(unsafe.Pointer(&rw.readerSem))race.Acquire(unsafe.Pointer(&rw.writerSem))}
}

①写锁操作只能被一个写协程拿到,竞争检测后进行互斥锁上锁Mutex.lock,,多协程时只能有一个协程拿到锁

②写锁时,readerCount减去rwmutexMaxReaders,readerCount会变成很大的负数,读锁时readerCount会+1

③如果readerCount不等于0,协程获取到读锁,如果满足上锁条件时,会调用runtime_SemacquireMutex,如果先进来的会在队列前面阻塞等待的信号量为writerSem,进入队列等待。

Unlock,解除写锁操作

func (rw *RWMutex) Unlock() {// ①竞争检测if race.Enabled {_ = rw.w.staterace.Release(unsafe.Pointer(&rw.readerSem))race.Disable()}// ②需要回复readerCount,上锁的时候减了常量rwmutexMaxReaders,这里再加回来r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders)// ③对未被写锁定的读写锁进行写解锁,会报错if r >= rwmutexMaxReaders {race.Enable()throw("sync: Unlock of unlocked RWMutex")}// ④呼唤阻塞的读操作for i := 0; i < int(r); i++ {runtime_Semrelease(&rw.readerSem, false, 0)}// ⑤释放锁,如果有其他阻塞写操作,在此唤醒rw.w.Unlock()if race.Enabled {race.Enable()}
}

①解除写锁操作时,竞争检测

②需要回复readerCount,上锁的时候减了常量rwmutexMaxReaders,这里再加回来,加完之后如果大于等于rwmutexMaxReaders

③对未被写锁定的读写锁进行写解锁,会抛异常

④呼唤阻塞的读操作

⑤释放锁,如果有其他阻塞写操作,在此唤醒

RLock,获取读锁操作

func (rw *RWMutex) RLock() {if race.Enabled {_ = rw.w.staterace.Disable()}// ①读锁时readerCount+1操作,当值小于0时会调用runtime_SemacquireMutex,表明写锁操作正在等待if atomic.AddInt32(&rw.readerCount, 1) < 0 {// 当写锁操作时,读锁也会阻塞等待runtime_SemacquireMutex(&rw.readerSem, false, 0)}if race.Enabled {race.Enable()race.Acquire(unsafe.Pointer(&rw.readerSem))}
}

①①读锁时readerCount+1操作,当值小于0时会调用runtime_SemacquireMutex,表明写锁操作正在等待

②当写锁操作时,读锁也会阻塞等待,读锁加入等待队列

RUnlock,解除读锁操作

func (rw *RWMutex) RUnlock() {if race.Enabled {_ = rw.w.staterace.ReleaseMerge(unsafe.Pointer(&rw.writerSem))race.Disable()}// ①直接将readerCount-1操作if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 {// ②如果r小于0,说明有协程正在获取写操作锁,// 当readerWait减到0时说明没有协程持有写操作锁,// 就通过信息号wrirerSem通知等待的协程来争抢锁rw.rUnlockSlow(r)}if race.Enabled {race.Enable()}
}

①解除读锁,直接对readerCount-1操作,这里就是把之前+1操作的再减回来

②如果r小于0,说明有协程正在获取写操作锁,当readerWait减到0时说明没有协程持有写操作锁,就通过信息号wrirerSem通知等待的协程来争抢锁

总结

  • 写锁操作时,对读写锁进行读锁定和写锁定,都会阻塞,读锁与写锁之间是互斥的。
  • 读锁操作时,对读写锁进行写锁定,会阻塞,加读锁时不会阻塞。
  • 对未被上锁的读|写锁进行解除锁定操作,会Panic。
  • 解除写锁操作时,同时会唤醒所有阻塞的读锁协程。
  • 解除读锁操作时,会唤醒一个因写锁操作而被阻塞的协程。
  • 读锁存在的时候,同时出现读锁和写锁操作,优先获取写锁。
  • 同时2个协程去争抢读锁和写锁时,都有机会抢成功。
  • 写锁存在时,同时开启2个协程去争抢读锁和写锁,优先获得读锁。

本文为原创文章,出自guichenglin,转载请粘贴源链接,如果未经允许转发后果自负。

Go语言 读写锁互斥锁原理剖析(1)相关推荐

  1. Go语言 读写锁互斥锁原理剖析(2)

    互斥锁(百科)定义:"在编程中,引入了对象互斥锁的概念,来保证共享数据操作的完整性.每个对象都对应于一个可称为" 互斥锁" 的标记,这个标记用来保证在任一时刻,只能有一个 ...

  2. 对变量移位顺序读写_Java多线程并发读写锁ReadWriteLock实现原理剖析

    关于读写锁 Java语法层面的synchronized锁和JDK内置可重入锁ReentrantLock我们都经常会使用,这两种锁都属于纯粹的独占锁,也就是说这些锁任意时刻只能由一个线程持有,其它线程都 ...

  3. 自旋锁/互斥锁/读写锁/递归锁的区别与联系

    自旋锁 互斥锁 读写锁 递归锁 互斥锁(mutexlock): 最常使用于线程同步的锁:标记用来保证在任一时刻,只能有一个线程访问该对象,同一线程多次加锁操作会造成死锁:临界区和互斥量都可用来实现此锁 ...

  4. Java并发原理抽丝剥茧,读写锁ReadWriteLock实现深入剖析

    跟着作者的65节课彻底搞懂Java并发原理专栏,一步步彻底搞懂Java并发原理. 作者简介:笔名seaboat,擅长工程算法.人工智能算法.自然语言处理.架构.分布式.高并发.大数据和搜索引擎等方面的 ...

  5. python互斥锁原理_Linux 互斥锁的实现原理(pthread_mutex_t)

    引言 互斥锁大都会使用,但是要了解其原理就要花费一番功夫了.尽管我们说互斥锁是用来保护一个临界区,实际上保护的是临界区中被操纵的数据. 互斥锁还是分为三类:快速互斥锁/递归互斥锁/检测互斥锁 fute ...

  6. C++ 互斥锁原理以及实际使用介绍

    兄弟姐妹们,我又回来了,今天带来实际开发中都需要使用的互斥锁的内容,主要聊一聊如何使用互斥锁以及都有哪几种方式实现互斥锁.实现互斥,可以有以下几种方式:互斥量(Mutex).递归互斥量(Recursi ...

  7. java读写锁降级_java的读写锁中锁降级的问题

    读写锁是什么我就不多说了,下面说什么是锁降级 锁降级: 锁降级指的是写锁降级成为读锁.如果当前线程拥有写锁,然后将其释放,最后再获取读锁,这种分段完成的过程不能称之为锁降级.锁降级是指把持住(当前拥有 ...

  8. python互斥锁原理_python并发编程之多进程1------互斥锁与进程间的通信

    一.互斥锁 进程之间数据隔离,但是共享一套文件系统,因而可以通过文件来实现进程直接的通信,但问题是必须自己加锁处理. 注意:加锁的目的是为了保证多个进程修改同一块数据时,同一时间只能有一个修改,即串行 ...

  9. C语言 读写锁pthread_rwlock_init

    友链 gcc 1.c -o 1 -lpthread 更高效 不会阻塞读操作 // ..使用内存映射可以拷贝文件 /* 对原始文件进行内存映射 创建一个新文件 把新文件的数据拷贝映射到内存中 通过内存拷 ...

最新文章

  1. github README.md教程
  2. 会议室管理前端页面_福州会议室钟联系方式
  3. Xamarin iOS教程之编辑界面编写代码
  4. 第一篇|腾讯开源项目盘点:WeUI,WePY,Tinker,Mars等
  5. Apache Camel 2.23发布
  6. linux 批量选中文本删除,教程 | 【七牛云】Qshell Linux下批量删除文件教程
  7. 【原创】MySQL Proxy - 协议(部分摘录)
  8. 第五节:STM32输入捕获(用CubeMX学习STM32)
  9. Prompt Learning | 一文带你概览Prompt工作新进展
  10. NSString copy or not (strong)?
  11. ThinkPHP的A方法,R方法,M方法,D方法区别
  12. win10电脑忘记开机密码的解锁方法
  13. 网站被劫持了怎么办?
  14. Writeup-GKCTF-Misc题:Harley Quinn
  15. Prompt+对比学习,更好地学习句子表征
  16. 【场景化解决方案】审批费用单自动同步至畅捷通
  17. 微型计算机输入设备 写出六种,计算机一级考试word题及答案
  18. 14个程序员常去的外国网站
  19. Linux服务器上部署项目以及问题记录
  20. Invalid bound statement (not found)错误的原因和解决办法

热门文章

  1. kafka 脚本发送_NWPC消息平台:在ecFlow系统中发送产品事件消息
  2. java pid 获取句柄_获取进程pid、根据进程pid获取线程pid、获取线程进程句柄
  3. java 保存inputstream_java如何将一个InputStream写入文件啊?
  4. java多线程传值覆盖_Java 多线程传值的四种方法
  5. linux shell命令行及脚本编程实例详解_超全整理!这些Shell编程必备知识你都掌握了吗?...
  6. office控件显示不了_计算机二级office考试重点难点总结,考生必看!
  7. 久谦咨询python笔试题目_【久谦咨询面试|面试题】-看准网
  8. git创建本地分支、提交到远程分支
  9. numpy的基本使用2
  10. R语言入门2---R语言基础绘图