图解golang里面的读写锁实现与核心原理分析

本文深入探讨了Golang中的读写锁特点,包括读锁的共享性导致的写锁饥饿问题,以及如何通过高低位与等待队列解决。详细解析了Golang读写锁的成员变量、读写锁的实现逻辑,特别是写锁对读锁的抢占机制,保证了写锁的公平性。此外,还讨论了可见性、原子性问题及其底层CPU指令的实现。

基础筑基

读写锁的特点

读写锁区别与互斥锁的主要区别就是读锁之间是共享的,多个goroutine可以同时加读锁,但是写锁与写锁、写锁与读锁之间则是互斥的

写锁饥饿问题

因为读锁是共享的,所以如果当前已经有读锁,那后续goroutine继续加读锁正常情况下是可以加锁成功,但是如果一直有读锁进行加锁,那尝试加写锁的goroutine则可能会长期获取不到锁,这就是因为读锁而导致的写锁饥饿问题

基于高低位与等待队列的实现

image.png 在说golang之前介绍一种JAVA里面的实现,在JAVA中ReentrantReadWriteLock实现采用一个state的高低位来进行读写锁的计数,其中高16位存储读的计数,低16位存储写的计数,并配合一个AQS来实现排队等待机制,同时AQS中的每个waiter都会有一个status,用来标识自己的状态

golang的读写锁的实现

成员变量

image.png

结构体

type RWMutex struct {
	w           Mutex  // held if there are pending writers
	writerSem   uint32 // 用于writer等待读完成排队的信号量
	readerSem   uint32 // 用于reader等待写完成排队的信号量
	readerCount int32  // 读锁的计数器
	readerWait  int32  // 等待读锁释放的数量
}
Golang 中互斥锁(`sync.Mutex`)和读写锁(`sync.RWMutex`)在底层实现上存在明显区别。 ### 数据结构 - **互斥锁**:互斥锁的结构体定义较为简单,主要由一个 `state` 字段和一个 `sema` 字段组成。`state` 字段使用位标记来表示不同的状态,低三位分别表示 `mutexed`、`mutexWoken` 和 `mutexStarving`,其余位表示等待锁的 goroutine 数量;`sema` 用于实现信号量,控制 goroutine 的阻塞和唤醒 [^4]。 ```go type Mutex struct { state int32 sema uint32 } ``` - **读写锁**:读写锁的结构体包含更多的字段,有 `w`(一个互斥锁,用于保护写操作的互斥访问)、`writerSem`(写操作的信号量,用于阻塞和唤醒写操作的 goroutine)、`readerSem`(读操作的信号量,用于阻塞和唤醒读操作的 goroutine)以及 `readerCount`(记录当前读操作的 goroutine 数量)和 `readerWait`(记录写操作等待时需要等待的读操作 goroutine 数量) [^2]。 ```go type RWMutex struct { w Mutex writerSem uint32 readerSem uint32 readerCount int32 readerWait int32 } ``` ### 加锁和解锁机制 - **互斥锁**:互斥锁的加锁(`Lock`)操作会尝试直接获取锁,如果锁已经被其他 goroutine 持有,则当前 goroutine 会被阻塞;解锁(`Unlock`)操作会释放锁,并唤醒一个等待的 goroutine。其加锁和解锁逻辑相对简单直接,主要围绕同一时间只允许一个 goroutine 访问临界区的原则 [^1]。 ```go var mu sync.Mutex mu.Lock() // 临界区代码 mu.Unlock() ``` - **读写锁**:读写锁的加锁和解锁操作分读锁和写锁。读锁的加锁(`RLock`)操作会增加 `readerCount`,允许多个 goroutine 同时获取读锁;读锁的解锁(`RUnlock`)操作会减少 `readerCount`。写锁的加锁(`Lock`)操作会先获取内部的互斥锁 `w`,然后将 `readerCount` 置为负数,表示有写操作正在进行,同时会等待所有正在进行的读操作完成;写锁的解锁(`Unlock`)操作会恢复 `readerCount` 的值,并释放内部的互斥锁 `w`,同时唤醒等待的读操作和写操作的 goroutine [^2]。 ```go var rwmu sync.RWMutex rwmu.RLock() // 读操作临界区代码 rwmu.RUnlock() rwmu.Lock() // 写操作临界区代码 rwmu.Unlock() ``` ### 并发控制策略 - **互斥锁**:互斥锁采用完全互斥的并发控制策略,同一时间只允许一个 goroutine 进入临界区,这种策略简单直接,但在高并发场景下,性能可能会受到影响,因为大量的 goroutine 会因为等待锁而被阻塞 [^1]。 - **读写锁**:读写锁采用了读写分离的并发控制策略,允许多个读操作同时进行,提高了读操作的并发性能。但在写操作时,会阻塞所有的读操作和其他写操作,以保证数据的一致性。这种策略在读多写少的场景下能显著提高性能 [^3]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值