RWMutex:读写锁源码解读

16 篇文章 0 订阅

RWMutex

设计思想

Write-preferring:写优先,如果有一个writer在等待写锁,那么会阻塞后面的读请求,优先保障writer

RWMutex采取的就是写优先思想,一个阻塞的Lock方法调用会阻止后面的RLock方法调用

源码解读

读锁
/*
	写写靠mutex进行阻塞,读写靠两个信号量进行阻塞
*/
type RWMutex struct {
	w           Mutex  // 用于写锁互斥
	writerSem   uint32 // 用于writer等待reader的信号量 
	readerSem   uint32 // 用于reader等待writer的信号量
	readerCount int32  // 当前reader数
	readerWait  int32  // writer等待完成的reader数
}

const rwmutexMaxReaders = 1 << 30

func (rw *RWMutex) RLock() {
	if atomic.AddInt32(&rw.readerCount, 1) < 0 {     //reader数+1,如果+1后的值小于0说明有writer在等待或者持有锁,那么阻塞reader
		runtime_SemacquireMutex(&rw.readerSem, false, 0)
	}
}

func (rw *RWMutex) RUnlock() {
	if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 {   //reader数-1,如果-1后的值小于0说明有writer在等待锁
		// Outlined slow-path to allow the fast-path to be inlined
		rw.rUnlockSlow(r)
	}
}

func (rw *RWMutex) rUnlockSlow(r int32) {
	// A writer is pending.
	if atomic.AddInt32(&rw.readerWait, -1) == 0 {    //writer等待的reader数-1,如果等于0那么唤醒writer
		// The last reader unblocks the writer.
		runtime_Semrelease(&rw.writerSem, false, 1)
	}
}

runtime_SemacquireMutex(s *uint32, lifo bool, skipframes int):
SemacquireMutex 与 Semacquire 类似,但用于分析竞争的互斥体。
如果 lifo 为 true,则将 waiter 排在等待队列的头部。
skipframes 是跟踪期间要省略的帧数,从runtime_SemacquireMutex 的调用者开始
唤醒后从调用方法头开始执行,skipframes是跳过的帧数

runtime_Semacquire(s uint32):
获取信号量, s原子递减1
如果
s大于0那么继续执行,否则阻塞等待直到*s>0
唤醒后从阻塞的地方继续执行

写锁
func (rw *RWMutex) Lock() {
	rw.w.Lock()     //加写锁
	// readerCount减readers的最大数量,对readerCount进行反转,用于读锁判断是否有写操作
	r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders
	if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 {   //当前有reader,那么将reader数设置为readerWait即writer等待的reader数,阻塞writer
		runtime_SemacquireMutex(&rw.writerSem, false, 0)
	}
}

func (rw *RWMutex) Unlock() {
	// readerCount加readers的最大数量,获取当前reader数
	r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders)
	// 唤醒阻塞的reader
	for i := 0; i < int(r); i++ {
		runtime_Semrelease(&rw.readerSem, false, 0)
	}
	// 释放写锁,唤醒阻塞的writer
	rw.w.Unlock()
}
总结
  1. 如果读多写少,那么大部分的读都只需要执行一次原子的Add操作就可以拿到锁不需要阻塞,相较于Mutex来说,效率更高
  2. 如果读少写多,那么大部分写除了要加Mutex的锁外,还要反转设置readerCount的值,判断readerWait的值,效率会一些
  3. 如果读写差不多,使用RWMutex读还是可以共同执行的只需要执行原子Add操作,RWMutex的效率高于Mutex

常见踩坑

不可复制

RWMutex也是具有状态的,复制后是一个新的RWMutex,原来的RWMutex状态发生变化,复制的RWMutex也不会有变化

可以借助vet工具检查

重入导致死锁
  1. 第一种场景
    内置写锁Mutex的重入
func MutexReentrant() {
	var rm sync.RWMutex
	rm.Lock()
	rm.Lock()
	rm.Unlock()
	rm.Unlock()
}
  1. 第二种场景
    有活跃的Reader,writer会等待活跃的Reader,活跃的Reader再调用Lock方法,那么活跃的Reader又会等待writer,形成环路等待
func MutexReentrant2() {
	var rm sync.RWMutex
	rm.RLock()
	go func() {
		rm.Lock()
		time.Sleep(2 * time.Millisecond)
		rm.Unlock()
	}()
	time.Sleep(1 * time.Millisecond)
	rm.Lock()
	rm.Unlock()
	rm.RUnlock()
}
  1. 第三种场景
    writer依赖活跃的reader -> 活跃的reader依赖新来的reader -> 新来的reader依赖writer
    比较复杂,一般不会出现
RLock、RUnLock不成对,Lock、UnLock不成对

RLock、RUnLock一定要成对
Lock、UnLock一定要成对

公众号关注

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
是用于多线程环境下保护共享资的一种机制。在 Linux 内核中,的实现是基于 spinlock 和原子操作的。下面我将简单介绍一下 Linux 内核中码实现。 的定义: ```c typedef struct { raw_rwlock_t raw_lock; } rwlock_t; ``` 其中,raw_rwlock_t 是一个原始类型,它是由内核提供的一个结构体类型,定义在 include/linux/spinlock_types.h 文件中。 raw_rwlock_t 的定义: ```c typedef struct { arch_rwlock_t raw_lock; } raw_rwlock_t; typedef struct { unsigned int lock; } arch_rwlock_t; ``` 其中,arch_rwlock_t 是一个体系结构相关的原始类型,它的实现由不同的处理器架构提供。 下面是 x86_64 架构下的 arch_rwlock_t 实现: ```c struct __arch_rwlock { unsigned int lock; }; typedef struct __arch_rwlock arch_rwlock_t; ``` 可以看到,在 x86_64 架构下,arch_rwlock_t 只包含一个 unsigned int 类型的 lock 成员变量,用于存储状态。 的初始化: ```c void rwlock_init(rwlock_t *lock) { raw_spin_lock_init(&lock->raw_lock.spinlock); atomic_long_set(&lock->raw_lock.rw_sem, 0); } ``` 其中,raw_spin_lock_init() 用于初始化,atomic_long_set() 用于初始化计数器。 的获取: ```c void read_lock(rwlock_t *lock) { while (1) { long count = atomic_long_read(&lock->raw_lock.rw_sem); if (count >= 0) { if (atomic_long_cmpxchg(&lock->raw_lock.rw_sem, count, count + 1) == count) { break; } } else { cpu_relax(); } } raw_spin_lock(&lock->raw_lock.spinlock); } ``` 其中,atomic_long_read() 用于计数器的值,如果值大于等于 0,则表示可用,此时使用 atomic_long_cmpxchg() 原子操作来增加计数器并获取;如果值小于 0,则表示有在使用,此时使用 cpu_relax() 函数等待释放。 的释放: ```c void read_unlock(rwlock_t *lock) { raw_spin_unlock(&lock->raw_lock.spinlock); atomic_long_dec(&lock->raw_lock.rw_sem); } ``` 其中,raw_spin_unlock() 用于释放,atomic_long_dec() 用于减少计数器的值。 的获取: ```c void write_lock(rwlock_t *lock) { raw_spin_lock(&lock->raw_lock.spinlock); while (1) { long count = atomic_long_read(&lock->raw_lock.rw_sem); if (count == 0) { if (atomic_long_cmpxchg(&lock->raw_lock.rw_sem, 0, -1) == 0) { break; } } else { cpu_relax(); } } } ``` 其中,raw_spin_lock() 用于获取,atomic_long_read() 用于计数器的值,如果值等于 0,则表示未被使用,此时使用 atomic_long_cmpxchg() 原子操作将计数器的值修改为 -1 并获取;如果值大于 0,则表示有在使用,此时使用 cpu_relax() 函数等待释放。 的释放: ```c void write_unlock(rwlock_t *lock) { atomic_long_set(&lock->raw_lock.rw_sem, 0); raw_spin_unlock(&lock->raw_lock.spinlock); } ``` 其中,atomic_long_set() 用于将计数器的值设为 0,raw_spin_unlock() 用于释放。 以上就是 Linux 内核中码实现。值得注意的是,在多处理器环境下,的实现可能会涉及到更复杂的机制,例如者优先等待、者优先等待等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值