如何掌握go的sync.RWMutex锁

发布时间:2023-03-09 16:31:43 作者:iii
来源:亿速云 阅读:152

本文小编为大家详细介绍“如何掌握go的sync.RWMutex锁”,内容详细,步骤清晰,细节处理妥当,希望这篇“如何掌握go的sync.RWMutex锁”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。

在简略的说之前,首先要对RW锁的结构有一个大致的了解

type RWMutex struct {
    w           Mutex  // 写锁互斥锁,只锁写锁,和读锁无关
    writerSem   uint32 // sema锁--用于“写协程”排队等待
    readerSem   uint32 // sema锁--用于“读协程”排队等待
    readerCount int32  // 读锁的计数器
    readerWait  int32  // 等待读锁释放的数量
}

这里要额外说一句,writerSem和readerSem底层都是semaRoot,这个结构体有兴趣可以了解下,他的用法有点类似于一个简版的channel,很多地方把他的初始值设置为0,使得所有想获取该sema锁的协程都排队等待,也就是说初始值为0的sema锁,他本身起到的作用是成为一个协程等待队列,就像没有缓冲区的channel一样。

前提:

readerCount这个参数非常重要

读写锁互斥性

一个很重要的参数:const rwmutexMaxReaders = 1 << 30 ,rwmutexMaxReaders 非常大,意思是最多能有rwmutexMaxReaders(1 << 30  十进制为  4294967296)个协程同时持有读锁。

写锁上锁场景:

首先分析写锁,因为读锁的很多操作是根据写锁来的,如果一上来就说读锁,很多东西没法串起来

 func (rw *RWMutex) Lock() {
    // race.Enabled是官方的一些测试,性能检测的东西,无需关心,这个只在编译阶段才能启用
	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.
	r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders
	// Wait for active readers.
	if 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))
	}
}

1.获取写锁--没有读锁等待

2.获取写锁--有读锁等待

3.获取写锁--前面已经有写锁了

后面的写协程也调用 rw.w.Lock() 进行加锁,因为前面有写锁已经获取了w,所以后续的写协程会因为获取不到w,而进入到w的sema队列里面,w是一个mutex的锁,mutex锁里是一个sema锁,sema锁因为没有设置初始值,所以退化为一个队列,而获取不到w锁的就会直接被阻塞在w的sema队列里,从而无法进行接下来的操作

写锁释放锁场景:

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.
	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()
	}
}

1.释放写锁--后面【没有】读锁等待

2.释放写锁--后面【有】读锁等待

3.释放写锁--后面有【写锁】等待

读锁上锁场景:

func (rw *RWMutex) RLock() {
    // race.Enabled都是测试用的代码,在阅读源码的时候可以跳过
	if race.Enabled {
		_ = rw.w.state
		race.Disable()
	}
    
	if atomic.AddInt32(&rw.readerCount, 1) < 0 {
		// A writer is pending, wait for it.
		runtime_SemacquireMutex(&rw.readerSem, false, 0)
	}
	if race.Enabled {
		race.Enable()
		race.Acquire(unsafe.Pointer(&rw.readerSem))
	}
}

1.获取读锁--此时没有写锁.

最简单的场景,协程对rw.readerCount进行原子操作加一,如果得到的结果为正数,说明获取读锁成功。

2.获取读锁--前方已经有写锁抢占了该锁

3.获取读锁--前方有写锁抢已经被抢占,后方有写锁等待

读锁释放锁场景:

func (rw *RWMutex) RUnlock() {
	if race.Enabled {
		_ = rw.w.state
		race.ReleaseMerge(unsafe.Pointer(&rw.writerSem))
		race.Disable()
	}
	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()
	}
}

1.释放读锁--后方没有写锁等待

2.释放读锁--后方有写锁等待

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.
	if atomic.AddInt32(&rw.readerWait, -1) == 0 {
		// The last reader unblocks the writer.
		runtime_Semrelease(&rw.writerSem, false, 1)
	}
}

这里是有个前提的,上面提到(详见上面的获取写锁的场景1),如果写协程进来想加写锁,需要把它需要等待的读锁数量从readerCount里赋值给readerWait。当它等待的读锁释放后,就需要用rUnlockSlow方法对readerWait进行减1,如果readWait == 0 ,说明这是最后一个需要等待的读锁也释放了,释放后就通知该写锁可以被唤醒了,锁给你了。

读到这里,这篇“如何掌握go的sync.RWMutex锁”文章已经介绍完毕,想要掌握这篇文章的知识点还需要大家自己动手实践使用过才能领会,如果想了解更多相关内容的文章,欢迎关注亿速云行业资讯频道。

推荐阅读:
  1. go语言如何删除数组元素
  2. go并发如何实现素数筛

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

go

上一篇:SpringBoot之怎么正确、安全的关闭服务

下一篇:internet临时文件如何清理

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》