摘要

在上一篇文章 golang 重要知识:mutex 里我们介绍了互斥锁 mutex 的相关原理实现。而且在 Go 里除了互斥锁外,还有读写锁 RWMutex,它主要用来实现读共享,写独占的功能。今天我们也顺便分析下读写锁,加深对 Go 锁的理解。

读写锁的实现原理

所谓的读写锁,其实就是针对下面的两种场景,对 Goroutine 之间的同步互斥进行控制:

  • 多个 goroutine 一起占有读锁,互不影响,可以继续自己后面的逻辑代码。
  • 写锁正在占有着,则后面的 goroutine 无论是要进行读锁占有,还是写锁占有,都将会被阻塞等待,直到当前的写锁释放。

弄清楚上面的场景需求后,实现就简单多了,关键就在于判断当前是否处于写锁状态即可,毕竟需要有阻塞等待的动作。

按照常规思路,我们一般会采用一个标识位来维护这个状态。然而,Go 官方却连这一步都省了。

利用了一个本来就得维护的读锁数量,在进行写锁占有时,使它变为负数。

后面有新进来的读写操作,只需要判断该值是否正负即可,负数则代表当前正在进行写锁占有,需要阻塞等待。

而在写锁占有结束后,该值又会恢复为正数,又可以进行新的读写操作了。

RWMutex 源码分析

接下来,我们到 src/runtime/rwmutex.go里具体分析下 RWMutex 的代码结构。

  1. type rwmutex struct {
  2. rLock mutex
  3. readers muintptr
  4. readerPass uint32
  5. wLock mutex
  6. writer muintptr
  7. readerCount uint32
  8. readerWait uint32
  9. }

RWMutex 的 Lock() 分析

  1. func (rw \*rwmutex) Lock() {
  2. lock(&rw.wLock)
  3. m := getg().m
  4. r := int32(atomic.Xadd(&rw.readerCount, -rwmutexMaxReaders)) + rwmutexMaxReaders
  5. lock(&rw.rLock)
  6. if r != 0 && atomic.Xadd(&rw.readerWait, r) != 0 {
  7. systemstack(func() {
  8. rw.writer.set(m)
  9. unlock(&rw.rLock)
  10. notesleep(&m.park)
  11. noteclear(&m.park)
  12. })
  13. } else {
  14. unlock(&rw.rLock)
  15. }
  16. }

RWMutex 的 RLock() 分析

  1. func (rw \*rwmutex) Rlock() {
  2. acquirem()
  3. if int32(atomic.Xadd(&rw.readerCount, 1)) < 0 {
  4. systemstack(func() {
  5. lock(&rw.rLock)
  6. if rw.readerPass > 0 {
  7. rw.readerPass -= 1
  8. unlock(&rw.rLock)
  9. } else {
  10. m := getg().m
  11. m.schedlink = rw.readers
  12. rw.readers.set(m)
  13. unlock(&rw.rLock)
  14. notesleep(&m.park)
  15. noteclear(&m.park)
  16. }
  17. })
  18. }
  19. }

RWMutex 的 Unlock() 分析

  1. func (rw \*rwmutex) Unlock() {
  2. r := int32(atomic.Xadd(&rw.readerCount, rwmutexMaxReaders))
  3. if r >= rwmutexMaxReaders {
  4. throw("unlock of unlocked rwmutex")
  5. }
  6. lock(&rw.rLock)
  7. for rw.readers.ptr() != nil {
  8. reader := rw.readers.ptr()
  9. rw.readers = reader.schedlink
  10. reader.schedlink.set(nil)
  11. notewakeup(&reader.park)
  12. r -= 1
  13. }
  14. rw.readerPass += uint32(r)
  15. unlock(&rw.rLock)
  16. unlock(&rw.wLock)
  17. }

RWMutex 的 RUnlock() 分析

  1. func (rw \*rwmutex) RUnlock() {
  2. if r := int32(atomic.Xadd(&rw.readerCount, \-1)); r < 0 {
  3. if r+1 == 0 || r+1 == -rwmutexMaxReaders {
  4. throw("runlock of unlocked rwmutex")
  5. }
  6. if atomic.Xadd(&rw.readerWait, \-1) == 0 {
  7. lock(&rw.rLock)
  8. w := rw.writer.ptr()
  9. if w != nil {
  10. notewakeup(&w.park)
  11. }
  12. unlock(&rw.rLock)
  13. }
  14. }
  15. releasem(getg().m)
  16. }

总结

RWMutex 通过 readerCount 的正负来判断当前是处于读锁占有还是写锁占有。

在处于写锁占有状态后,会将此时的 readerCount 赋值给 readerWait,表示要等前面 readerWait 个读锁释放完才算完整的占有写锁,才能进行后面的独占操作。

读锁释放的时候, 会对 readerWait 对应减一,直到为 0 值,就可以唤起写锁了。

并且在写锁占有后,即时有新的读操作加进来, 也不会影响到 readerWait 值了,只会影响总的读锁数目:readerCount。


感兴趣的朋友可以搜一搜公众号「 阅新技术 」,关注更多的推送文章。
可以的话,就顺便点个赞、留个言、分享下,感谢各位支持!
阅新技术,阅读更多的新知识。
golang 系列:RWMutex 读写锁分析 - SegmentFault 思否 - 图1
https://segmentfault.com/a/1190000040485266