简介
简述sync包中读写锁的源码。 (go -version 1.21)
读写锁(RWMutex)是一种并发控制机制,用于在多个 goroutine 之间对共享资源进行读写操作。它提供了两种锁定方式:读锁和写锁。
读锁(RLock):多个 goroutine 可以同时持有读锁,而不会阻塞彼此。只有当没有写锁被持有时,读锁才会被授予。这样可以实现多个 goroutine 并发地读取共享资源,提高程序性能。
写锁(Lock):写锁是排它的,当某个 goroutine 持有写锁时,其他所有 goroutine 都无法获得读锁或写锁。这是为了确保在写入共享资源时,没有其他 goroutine 在读或写该资源。
写锁是排他性的 :
(假设写锁不是排他性的, 新的读锁可以被获取) 反证:(多个写锁的情况下就不聊了)
现在有 一个goroutine1 获取读锁; 一个 goroutine2 获取写锁,但没有写锁被其他goroutine持有
然后有个goroutine3 想获取读锁,在假设的条件下 goroutine3 就会获取到读锁, 会导致goroutine2 无法获取写锁, 极端情况下会导致goroutine2 一直获取不到写锁 ---- 写锁饥饿
所以 为了读写公平, 还是把写锁优先级提高, 在写锁的情况下, 新的读锁无法被获取。
源码
结构
// RWMutex 结构体包含了用于读写互斥锁的各种状态和信号量。在 RWMutex 中,多个 goroutine 可以同时持有读锁, // 但只有一个 goroutine 可以持有写锁。RWMutex 的实现确保了在写锁等待的情况下,新的读锁无法被获取, // 从而防止了读锁长时间等待 type RWMutex struct { w Mutex // 用于写锁的互斥锁 writerSem uint32 // 写锁的信号量,用于等待读锁完成 readerSem uint32 // 读锁的信号量,用于等待写锁完成 readerCount atomic.Int32 // 读者持有读者锁的数量 readerWait atomic.Int32 // 写者等待读锁的数量 } // readerCount // 当读者加锁时, readerCount +1 // 当读者解锁时, readerCount -1 // 当 readerCount 大于 0 时,表示有读者持有读锁。 // 如果 readerCount 等于 0,则表示当前没有读者持有读锁。 // 当 readerCount 等于 0 时,其他写者可以尝试获取写锁 // 当 readerCount < 0 , 说明有写者在等待, 读者需要等待写者释放写锁 // readerWait(写者等待读锁的数量): // 当写者尝试获取写锁,但当前有读者持有读锁时,写者会被阻塞,并且 readerWait 会增加。 // 当读者释放读锁时,如果有写者在等待读锁,readerWait 会减少,并且可能唤醒等待的写者 // readerCount > 0:表示有读者持有读锁。 // readerCount == 0 且 readerWait > 0:表示有写者在等待读锁,阻塞中。 // readerCount == 0 且 readerWait == 0:表示当前没有读者持有读锁,且没有写者在等待读锁。此时其他写者可以尝试获取写锁。 // 这样的设计是为了在写者等待读锁时,不允许新的读者加入,以确保写者获得更公平的机会。
RLock
// Happens-before relationships are indicated to the race detector via: // - Unlock -> Lock: readerSem // - Unlock -> RLock: readerSem // - RUnlock -> Lock: writerSem // // happends-before 用于描述时间发生的顺序,在 这里 (代码中的注释) // Unlock 事件发生之前(happens-before)的 Lock 事件。 // Unlock 事件发生之前(happens-before)的 RLock 事件。 // RUnlock 事件发生之前(happens-before)的 Lock 事件 func (rw *RWMutex) RLock() { if race.Enabled { _ = rw.w.state race.Disable() } // 它将 readerCount 增加 1。如果结果小于 0,说明有一个写者拿着锁了, 这边需要等着 if rw.readerCount.Add(1) < 0 { // A writer is pending, wait for it. // 在读写锁的情况下 执行锁的信号量 // 实际得等待操作, 调用底层代码, 等写者释放锁 runtime_SemacquireRWMutexR(&rw.readerSem, false, 0) } if race.Enabled { race.Enable() race.Acquire(unsafe.Pointer(&rw.readerSem)) } }
race.Enabled : 竞态检测, 是go运行时提供的工具, 用于检测并发程序中的数据竞争问题,
使用 go run -race main.go 可以检测, 然后输出报告。
后面竞态检测代码 不说明
RUnlock
// RUnlock undoes a single RLock call; // it does not affect other simultaneous readers. // It is a run-time error if rw is not locked for reading // on entry to RUnlock. func (rw *RWMutex) RUnlock() { ... // 竞态规则逻辑 // readerCount 用于记录当前持有读锁的读者数量。如果读者计数减为负数,说明存在写者正在等待读锁释放 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) { ... // 竞态规则逻辑 // A writer is pending. // 减少读者等待计数。readerWait 记录正在等待读锁释放的读者数量。如果读者等待计数减为零, // 说明最后一个读者已经释放了读锁,可以唤醒等待的写者 if rw.readerWait.Add(-1) == 0 { // The last reader unblocks the writer. // 释放写者的信号量 runtime_Semrelease(&rw.writerSem, false, 1) } }
func (rw *RWMutex) Lock() { ... // 竞态规则逻辑 // First, resolve competition with other writers. // 获取写锁,解决与其他写者的竞争 rw.w.Lock() // Announce to readers there is a pending writer. // 将 readerCount 减去 rwmutexMaxReaders,然后再加上 rwmutexMaxReaders,目的是将 readerCount 设置为负数, // 表示有一个写者正在等待写锁。这会通过 readerWait 记录下来,并用于通知读者和写者。 // 这边得 rw.readerCount 是一个负数, 在RLock 中有个判断 rw.readerCount <0 , // 这一段就是实现了写者优先, 不管当前有没有读者拿着读锁, 接下来拿锁的读锁, 统统排我后面,不能影响我(写者), 等我(写者)处理完了再说 r := rw.readerCount.Add(-rwmutexMaxReaders) + rwmutexMaxReaders // Wait for active readers. // 如果有活跃的读者,且将 readerWait 增加 r 后不为零,说明有读者正在等待读锁释放 if r != 0 && rw.readerWait.Add(r) != 0 { // 获取写锁。 // 它不是马上就能获取写锁,而是可能会被阻塞,需要等待写锁的释放 // 当写者执行这个操作时,如果当前没有其他写者或读者持有锁,那么它会成功获取写锁,否则它会被阻塞,直到没有其他写者或读者持有锁 runtime_SemacquireRWMutex(&rw.writerSem, false, 0) } ... // 竞态规则逻辑 }
Unlock
// arrange for another goroutine to RUnlock (Unlock) it. func (rw *RWMutex) Unlock() { ... // 竞态规则逻辑 // Announce to readers there is no active writer. // 将 readerCount 增加 rwmutexMaxReaders,用于通知活跃的读者,写者已经释放了写锁 // 我( 写者)处理完了, 把 rw.readerCount 加回来了, 当前写锁写完了, 刚刚我(写者)拿锁以后 申请的读锁们, 可以唤醒了 r := rw.readerCount.Add(rwmutexMaxReaders) ... // 竞态规则逻辑 // Unblock blocked readers, if any. // 遍历并逐个释放等待的读者,通过 runtime_Semrelease 信号量操作通知它们。 for i := 0; i < int(r); i++ { runtime_Semrelease(&rw.readerSem, false, 0) } // Allow other writers to proceed. rw.w.Unlock() ... // 竞态规则逻辑 }
go 运行时方法
1、runtime_SemacquireRWMutexR(&rw.readerSem, false, 0):
这个方法用于获取读锁。
&rw.readerSem 是一个信号量,表示等待读锁的读者队列。
false 表示不以 LIFO(LastIn, First Out)模式进行等待队列的管理。 、
0 表示无超时。
2、runtime_SemacquireRWMutex(&rw.writerSem, false, 0):
这个方法用于获取写锁。
&rw.writerSem 是一个信号量,表示等待写锁的写者队列。
false 表示不以 LIFO模式进行等待队列的管理。
0 表示无超时。
3、runtime_Semrelease(&rw.writerSem, false, 1):
这个方法用于释放写锁。
&rw.writerSem 是写锁等待队列的信号量。
false 表示不以 LIFO 模式进行等待队列的管理。
1 表示释放一个写者,通知等待的写者。
4、runtime_Semrelease(&rw.readerSem, false, 0):
这个方法用于释放读锁。
&rw.readerSem 是读锁等待队列的信号量。
false 表示不以 LIFO 模式进行等待队列的管理。
0表示释放一个读者,通知等待的读者
到此这篇关于go中sync.RWMutex的源码解读的文章就介绍到这了,更多相关go sync.RWMutex内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!