首页 > 其他分享 >go读写锁

go读写锁

时间:2023-12-01 19:57:08浏览次数:32  
标签:rw 协程 读写 读协程 readerWait go 写协程 readerCount

go读写锁

互斥锁每次只让一 g通过,去读写数据。但是读数据操作,并发其实没有问题。所以诞生了 读写锁。

读协程可以并发,一起读。但是 写协程还是要走互斥锁,只能一个个通过。

先加了读锁

先加了读锁。那么写的协程,就需要去休眠队列中等待。一直到读锁都释放。

先加了写锁

这个时候,不管再来 写协程还是读协程,都去休眠队列等待。

小结:

  没有加写锁时,多个协程都可以加读锁
  加了写锁时,无法加读锁,读协程排队等待
  加了读锁,写锁排队等待

定义

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 atomic.Int32 // number of pending readers
	readerWait  atomic.Int32 // number of departing readers
}

w:互斥锁作为写锁
writerSem:作为写协程队列
readerSem:作为读协程队列
readerCount: 正值:正在读的协程 负值:加了写锁
readerWait:写锁应该等待读协程个数

有三个sema队列了,w本身底层有一个,readerSemwriterSem

运行逻辑

当锁是一个初始化的状态,来了一个写协程

`rwmutexMaxReaders` 是一个非常大的常量

把readerCount 改成了一个 很大的负数

加写锁,有读协程已经在读了

来了写锁后,把 readerCount 也减去了一个很大的数,但是 3还是能从这个值中体现。
但是,当再有 读的g过来时候,发现readerCount 为负数,就会去readSem中休眠。

代码实现

读锁

加锁

func (rw *RWMutex) RLock() {
    
    // 将readerCount+1,发现还是负的,就去休眠,说明有写锁在等待
	if rw.readerCount.Add(1) < 0 {
		runtime_SemacquireRWMutexR(&rw.readerSem, false, 0)
	}
    // 如果不是负数,则加锁成功,去读数据
}

小结:

将给readerCount无脑加-
如果readerCount是正数,加锁成功
如果readerCount是负数,说明被加了写锁,陷入`readerSem`

解锁

func (rw *RWMutex) RUnlock() {
	// 将 readerCount 减去1, 如果 r 小于0,说明有写协程 在等待
	if r := rw.readerCount.Add(-1); r < 0 {
		// Outlined slow-path to allow the fast-path to be inlined
		rw.rUnlockSlow(r)
	}
}

func (rw *RWMutex) rUnlockSlow(r int32) {
    
    // readerWait  写锁应该等待读协程个数 减去1(自身) 之后,
    //如果是 0,说明没有 读协程在读取数据了 ,就去 `runtime_Semrelease `唤醒换一个写协程
	if rw.readerWait.Add(-1) == 0 {
		// The last reader unblocks the writer.
		runtime_Semrelease(&rw.writerSem, false, 1)
	}
}

读锁在解锁时候,去判断了 readerCount 是否小于0 是否有写协程在等待;然后再释放写协程,又判断了 readerWait 是否等于0,因为大于0,说明还有读协程。

小结:

  给readerCountit减1
  如果readerCount是正数,解锁成功,没有写协程在排队
  如果readerCount是负数,有写锁在排队
  如果自己是readerWait的最后一个,唤醒写协程

问题: 但是读锁在加锁时候,并没有 给 readerWait加值,这里判断是否有效呢 ?

写锁

加锁
func (rw *RWMutex) Lock() {

	rw.w.Lock() // 先去加互斥锁,加上了再执行下面的逻辑,加不上直接去 w的sema中休眠了
	r := rw.readerCount.Add(-rwmutexMaxReaders) + rwmutexMaxReaders
    // 判断 r是否为0, 等于0 则直接加锁成功了。
	if r != 0 && rw.readerWait.Add(r) != 0 {
		runtime_SemacquireRWMutex(&rw.writerSem, false, 0)
	}
}

讲下这里不为0的情况,如果r不为0,比如r为2,则说明还有2个读协程在工作。

rw.readerWait.Add(r)这句代码,正好解释了上面的问题。这里给 readerWait 赋值了,所以读协程在解锁时候,判断这个值才有用。

如果不来写协程,那这个 readerWait 没有意义,因为这是判断是否释放写协程的。

那有没有lock()被两个 写协程 先后连续执行,让 r 的值为一个很大的负数?

不会。因为要先去加 互斥锁。一个写协程加上后,其他的写协程只能去 sema中等待。上篇有讲过。 所以上面有一个举例的图其实是有问题的。

小结加写锁:

先加mutex写锁,若已经被加写锁会阻塞等待
将readerCount变为负值,阻塞读锁的获取
计算需要等待多少个读协程释放
如果需要等待读协程释放,陷入writerSem

解写锁

func (rw *RWMutex) Unlock() {
	
	r := rw.readerCount.Add(rwmutexMaxReaders)
    // 去释放读协程,没有去释放 `writerSem`里面的写协程,因为这里面根本不会有休眠的写协程
	for i := 0; i < int(r); i++ {
		runtime_Semrelease(&rw.readerSem, false, 0) // 释放读协程
	}
	rw.w.Unlock() // 解锁 互斥锁,让互斥锁的sema等待队列中的协程,重新去竞争锁
}

小结 解写锁 :

  将readercount变为正值,允许读锁的获取

  释放在readerSem中等待的读协程 上面讲了,这个时候 writerSem 是空的 

  解锁mutex

适合场景

适合读多写少的场景,如果都是写的场景,其实和互斥锁一样。

标签:rw,协程,读写,读协程,readerWait,go,写协程,readerCount
From: https://www.cnblogs.com/studyios/p/17870674.html

相关文章

  • 记一次vscode 打开go项目的处理方式
    问题:需要用vscode打开没有用go.mod管理的项目打包项目为linux执行的二进制文件vscode全局settings.json配置{"go.formatTool":"gofmt","go.gopath":"D:\\GoPath;","go.goroot":"D:\\GO","go.lintTool&......
  • jwt在go中的应用
    官网JWT什么是JWT在现代的Web应用开发中,目前已经有大半部分的应用都是使用的jwt的方式来做登录鉴权功能,那么什么是jwt呢?JSONWebToken(JWT)是一个开放标准RFC519,它定义了一种紧凑且自包含的方式,用于作为JSON对象在各方面之间安全地传输信息;JWT是一个数......
  • Django补4
    过滤器写一个过滤器---》一堆内容---》经过过滤器后---》把关键词屏蔽#自定义过滤器{{变量|过滤器名字}}编写步骤1注册app2在某个app下:创建templatetags模块(模块名只能是templatetags)3在包下写一个py文件,随便命名4在py文件中:写入fromdjangoimporttemplateregister=t......
  • 【解决】模拟器设置system读写报错'/dev/block/sda6' is read-only mount: '/system'
    remount失败'/dev/block/sda6'isread-onlyadbdisable-verityfailedtoreadfstab'/dev/root'isread-onlymount-oremount,rw/systemmount:'/system'notin/proc/mountsmount-orw,remount-tauto/主要是设置没调好模拟器需要打开可写系统盘或......
  • GORM学习
    Day1:GORM入门1.环境的安装在项目文件的terminal中输入下面两条命令进行gorm安装gogetgorm.io/driver/mysqlgogetgorm.io/gorm2.安装好之后使用以下代码进行检测,其中的地址拼接是重点"%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local&timeout=%s",us......
  • Google Play 结算系统
    技术GooglePlay。供用户下载应用及其他数字商品的在线商店。GooglePlay管理中心。提供界面,供您将应用发布到GooglePlay的平台。GooglePlay管理中心还会显示您的应用详情,包括您通过GooglePlay销售的任何商品或内容。GoogleCloud控制台。用于管理后端API(例如Google......
  • Go--命名规则
    在Go语言中,项目名和文件名的命名规则有一些建议和惯例。以下是一些常见的规则和最佳实践:项目名:项目名应该简短、有意义,并能够清晰地表达项目的目的或功能。项目名通常使用小写字母,使用连字符或下划线分隔单词。项目名不应包含特殊字符或空格。项目名应尽量避免与现有的库......
  • Google Play 允许区块链游戏和 NFT 应用进入平台
    为GameFi用户在地域分布与手机机型分布方面与GooglePlay 有众多契合之处:地域分布:东南亚地区用户占比最大,2022年上半年东南亚用户占比达到41%其次是北美和西欧地区用户,2022年上半年占比分别为16%和15%发展中国家用户占比也在快速增长,如菲律宾、越南、印度等机......
  • MongoTemplate操作MongoDB
    集成简介spring-data-mongodb提供了MongoTemplate与MongoRepository两种方式访问mongodb,MongoRepository操作简单,MongoTemplate操作灵活,我们在项目中可以灵活适用这两种方式操作mongodb,MongoRepository的缺点是不够灵活,MongoTemplate正好可以弥补不足。搭建开发环境1、创建springbo......
  • go锁基础 - atomic、sema
    atomic和sema是实现go中锁的基础,简单看下他们的实现原理。atomic`atomic常用来作为保证原子性的操作。当多个协程,同时一个数据进行操作时候,如果不加锁,最终的很难得到想要的结果。varpint64=0funcadd(){ p=p+1}funcmain(){ fori:=0;i<10......