GoLang sync.RWLock 实现分析

读写锁

GoLang sync.RWLock 读写锁实现分析,宏观上读写锁具有的功能如下:

读锁写锁
读锁
写锁

总结: 读锁加读锁,写锁全拒绝

怎么实现

如果是你,你会怎样设计,请将想法在评论区留言?如果是我,我会这样想:

  • 定义读操作计数readerCount,锁定加一,解锁减一。就是对readerCount进行PV操作

  • 定义写操作计数writerCount,锁定加一,解锁减一。就是对writerCount进行PV操作

  • readerCount 有区间范围 [0, 1<<32)

  • writerCount 有区间范围 [0, 1)

  • 加读锁检查写锁计数,有值则阻塞读操作,否则放行

  • 加写锁检查写锁和读锁计数,有值则阻塞写操作,否则放行

读写锁定义


type RWMutex struct {
	w           Mutex  # held if there are pending writers
	writerSem   uint32 # semaphore for writers to wait for completing readers
	readerSem   uint32 # semaphore for readers to wait for completing writers
	readerCount int32  # number of pending readers
	readerWait  int32  # number of departing readers
}

const rwmutexMaxReaders = 1 << 30

属性说明
w互斥锁,保护写操作
writerSem写操作信号量,用于阻塞或唤醒goroutine
readerSem读操作信号量, 用于阻塞或唤醒goroutine
readerCount读操作计数
readerWait写锁锁定时,快照当前读操作计数
rwmutexMaxReaders读操作上限

写锁锁定

#注意: race 用于竞态分析 比如 go run -race main.go

func (rw *RWMutex) Lock() {
	if race.Enabled {
		_ = rw.w.state
		race.Disable()
	}
    #通过互斥锁保护写操作,若已存在写锁,那么运行到这里时阻塞
	rw.w.Lock()

    #写锁时检查读操作计数rw.readerWait,
    # 
    #   r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders
    #
    #那么r就是运行前的rw.readerWait且r >= 0,
    #
    #  rw.readerCount 变成负数
    #
    #也就表明当rw.readerCount为负数时,存在写操作等待
    #
	r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders

    # r  = 0, 表示没有读锁,可以执行写操作
    # r != 0, 存在读操作,那么阻塞写操作,同时记录待执行读操作的数量

	if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 {
        #
        #runtime_SemacquireMutex runtime库提供
        #
		runtime_SemacquireMutex(&rw.writerSem, false, 0)
	}

	if race.Enabled {
		race.Enable()
		race.Acquire(unsafe.Pointer(&rw.readerSem))
		race.Acquire(unsafe.Pointer(&rw.writerSem))
	}
}

runtime_SemacquireMutex

读锁锁定

#注意: race 用于竞态分析 比如 go run -race main.go

func (rw *RWMutex) RLock() {
	if race.Enabled {
		_ = rw.w.state
		race.Disable()
	}
    #
    # atomic.AddInt32(&rw.readerCount, 1) 即
    #
    #   rw.readerCount = rw.readerCount + 1
    #
    # rw.readerCount > 0 无写操作,都是度操作
    #
    # rw.readerCount < 0 存在申请写操作, 因为在写锁操作中将 rw.readerCount 设置为负数, 并阻塞读操作
    #
	if atomic.AddInt32(&rw.readerCount, 1) < 0 {
        #
        #runtime_SemacquireMutex runtime库提供
        #
		runtime_SemacquireMutex(&rw.readerSem, false, 0)
	}

	if race.Enabled {
		race.Enable()
		race.Acquire(unsafe.Pointer(&rw.readerSem))
	}
}

runtime_SemacquireMutex

写锁解锁

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

    #
    # r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders)
    #
    # 将rw.readerCount 设置成正数,恢复读锁锁定
    #
    #GoLang 将rw.readerCount 正负区分读写操作,实在是妙
    #
	r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders)

    #
    #超过区间范围,报错
    #
    #例如:
    #   未写锁锁定,直接写锁解锁,那么r = rwmutexMaxReaders  
    #     读锁锁定,直接写锁解锁,那么r > rwmutexMaxReaders  
	if r >= rwmutexMaxReaders {
		race.Enable()
		throw("sync: Unlock of unlocked RWMutex")
	}

    #
    #唤醒全部被阻塞的读操作
    #
	for i := 0; i < int(r); i++ {
		runtime_Semrelease(&rw.readerSem, false, 0)
	}

    #
    # 释放互斥锁,允许下一个写操作
    #
	rw.w.Unlock()

	if race.Enabled {
		race.Enable()
	}
}

runtime_Semrelease

读锁解锁


func (rw *RWMutex) RUnlock() {
	if race.Enabled {
		_ = rw.w.state
		race.ReleaseMerge(unsafe.Pointer(&rw.writerSem))
		race.Disable()
	}
    #
    # r := atomic.AddInt32(&rw.readerCount, -1)
    #
    # 若无写锁,那么 r >= 0 ,直接放行,称之为快路径
    #
    # 若有写锁,那么 r < 0, 进入慢路径
    #
	if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 {
		rw.rUnlockSlow(r)
	}

	if race.Enabled {
		race.Enable()
	}
}

#
# r < 0 
#
# r + 1 = 0:  
#   (1)意味着读操作数量已超过设定值。写锁锁定将rw.readerCount - rwmutexMaxReaders, 意味rw.readerCount 将会从-rwmutexMaxReaders增长到-1
#
#   (2)未读锁锁定,直接读锁解锁
#
# r + 1 = -rwmutexMaxReaders:
#   (1)极限情况读操作数量rwmutexMaxReaders时申请写操作,那么rw.readerCount = rwmutexMaxReaders - rwmutexMaxReaders = 0
#   在写锁未解锁前,读操作陆续解锁,即rw.readerCount--,直到rw.readerCount = -rwmutexMaxReaders。若继续解锁,那么报错
#
#
func (rw *RWMutex) rUnlockSlow(r int32) {
	if r+1 == 0 || r+1 == -rwmutexMaxReaders {
		race.Enable()
		throw("sync: RUnlock of unlocked RWMutex")
	}
    #
    # rw.readerWait 是申请写操锁时当前待执行读操作的数量
    #
    # rw.readerWait = 0 表示已无操作,可以唤醒写操作
    #
	if atomic.AddInt32(&rw.readerWait, -1) == 0 {
		runtime_Semrelease(&rw.writerSem, false, 1)
	}
}

runtime_Semrelease

总结

GoLang sync.RWLock 基于sync.Mutex实现,逻辑清晰,简化实现。 sync.Mutex实现相对复杂,下期分享

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cugriver

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值