go sync.RWMutex 源码解析

一、RWMutex是什么?

RWMutex是一种读写互斥锁。该锁可以由任意数量的读取器或单个写入器持有。

RWMutex的0值是一个未锁定的互斥锁。
RWMutex在第一次使用后不能被复制。
如果一个goroutine持有一个用于读取的RWMutex,而另一个goroutine可能会调用Lock,那么在读锁被释放之前,没有goroutine应该能够获得读锁。特别是,这禁止递归读锁定。这是为了确保锁最终变得可用;

二、RWMutex 结构体

type RWMutex struct {
	w           Mutex  // 互斥锁
	writerSem   uint32 // 写入器等待读取器完成的信号量
	readerSem   uint32 // 让读取器等待写入器完成的信号量
	readerCount int32  // 正在读取的goroutine数量
	readerWait  int32  // 正在等待读取的goroutine数量
}

w(Mutext)在这里的作用:如Mutex中有等待的goroutine,RWMutex会暂停使用,直到写锁完全释放。

到这里,应该可以直到RWMutex是一个写优先的互斥锁.

三、RWMutex 方法

1.RLock - 获取读锁

func (rw *RWMutex) RLock() {
	if race.Enabled {
		_ = rw.w.state
		race.Disable()
	}
	if atomic.AddInt32(&rw.readerCount, 1) < 0 {
		runtime_SemacquireMutex(&rw.readerSem, false, 0)
	}
	if race.Enabled {
		race.Enable()
		race.Acquire(unsafe.Pointer(&rw.readerSem))
	}
}

RLock很简单,先操作readerCount原子性(AddInt32) + 1,通过返回值来判断当前是否有goroutine持有写锁;若有goroutine持有写锁,则当前goroutine进入休眠,在readerSem信号中排队(排到最后一位).

2.RUnlock - 释放读锁

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 {
		// 慢路径释放读锁
		rw.rUnlockSlow(r)
	}
	if race.Enabled {
		race.Enable()
	}
}

同样,释放读锁是readerCount来判断当前是否有goroutine持有写锁。

func (rw *RWMutex) rUnlockSlow(r int32) {
    //判断当前读取goroutine的数量(readerCount),若没有正在读取的,则panic
	if r+1 == 0 || r+1 == -rwmutexMaxReaders {
		race.Enable()
		throw("sync: RUnlock of unlocked RWMutex")
	}
	// 若等待读取的goroutine(readerWait)数量为0,则唤醒等待的写锁(writerSem)
	if atomic.AddInt32(&rw.readerWait, -1) == 0 {
		// The last reader unblocks the writer.
		runtime_Semrelease(&rw.writerSem, false, 1)
	}
}

3.Lock - 获取写锁

func (rw *RWMutex) Lock() {
	if race.Enabled {
		_ = rw.w.state
		race.Disable()
	}
	// 首先获取写锁,通过Mutex来实现
	rw.w.Lock()
	// 修改readerCount值,将当前正在读取的goroutine修改为负数
	r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders
	// 修改readerWait值,将readerCount数量添加到readerWait;readerWait += readerCount
	if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 {
		runtime_SemacquireMutex(&rw.writerSem, false, 0)//将当前goroutine进行睡眠,并排队到writerSem队列的队尾
	}
	if race.Enabled {
		race.Enable()
		race.Acquire(unsafe.Pointer(&rw.readerSem))
		race.Acquire(unsafe.Pointer(&rw.writerSem))
	}
}

4. Unlock - 释放写锁

func (rw *RWMutex) Unlock() {
	if race.Enabled {
		_ = rw.w.state
		race.Release(unsafe.Pointer(&rw.readerSem))
		race.Disable()
	}

	// 恢复readerCount值
	r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders)
	if r >= rwmutexMaxReaders {//若readerCount异常,则panic
		race.Enable()
		throw("sync: Unlock of unlocked RWMutex")
	}
	// 唤醒所有等待获取读锁的goroutine
	for i := 0; i < int(r); i++ {
		runtime_Semrelease(&rw.readerSem, false, 0)
	}
	// Allow other writers to proceed.
	rw.w.Unlock() //释放写锁(Mutex.Unlock)
	if race.Enabled {
		race.Enable()
	}
}

通过上面的源码分析,可以看到获取读锁/写锁时,当获取失败时,通过信号量(readerSem/writerSem)进入睡眠并排在相应队列的队尾;而在释放读锁/写锁时,也是通过相应的信号量(readerSem/writerSem)进行唤醒。

为什么RWMutex是写优先?

在Lock方法中,获取写锁时,是直接使用Mutex.Lock方法获取,而不是等待获得读锁的goroutine读取完成后,进行释放。

Mutex.Lock方法在获取失败时,会进行自旋、进入睡眠,直到获得锁。https://mp.csdn.net/mp_blog/creation/editor/125683672

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值