背景

最近项目中遇到两次RWMutex死锁问题,所以稍微看了一下资料和源码,稍作记录

源码

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
}

RWMutex源码分析方面参考资料《go中的sync.RWMutex源码解读》写的比较详细和清晰易懂。

总结

go中锁都是不可重入的,所以同一个协程中获取两次RWmutex锁都可能出现死锁

1. 同一协程获取两次RLock

  此场景参考前一篇文章《golang RWMutex RLock重入导致死锁》,同一协程两次获取RLock,如果在第二次获取RLock之前,有其他协程获取写锁Lock则会导致死锁。

  这种场景和同一协程先获取Lock再获取RLock是一样的原理,由于协程1获取了RLock,导致协程2获取Lock时会被阻塞,然而协程2获取Lock时会将rw.readerCount置为<0的负值,然后协程1再获取RLock也会被阻塞,所以导致两个协程相互阻塞了。

2. 同一协程先获取RLock再获取Lock

func TestLockUp(t *testing.T) {
var l sync.RWMutex
l.RLock()
t.Log("acquire read lock")
l.Lock()
t.Log("acquire write lock")
l.Unlock()
l.RUnlock()
}

  读锁是会阻塞写锁的;从源代码中可以看到,在获取Lock时,如果已经有协程获取了RLock,则获取Lock的协程会阻塞在获取rw.writerSem信号量上,在读锁RLock解锁时会唤醒该信号量,然后RLock在等待Lock解锁才能执行RUnLock,因此造成死锁。

3. 同一协程先获取Lock再获取RLock

func TestLockDown(t *testing.T) {
var l sync.RWMutex
l.Lock()
t.Log("acquire write lock")
l.RLock()
t.Log("acquire read lock")
l.RUnlock()
l.Unlock()
}

  写锁也会阻止读锁;从源代码中可以看到,当一个协程获取写锁Lock时,会将rw.readerCount置为<0的负值,而当获取读锁RLock时,先对rw.readerCount加1,如果加1后的结果为负值,则表示有协程已经获取到写锁或者正在等待获取写锁,因而该获取读锁的协程会阻塞在获取rw.readerSem信号量上;因此同一个协程先后获取Lock和RLock会相互阻塞等待从而造成死锁。

  从这里也能看出来,在RWMutex中,写锁的优先级高于读锁,只要有协程在等待获取写锁,后续的读锁都需要等待。

4. 同一协程获取两次Lock

func TestReLock(t *testing.T) {
var l sync.RWMutex
l.Lock()
t.Log("acquire write lock")
l.Lock()
t.Log("acquire write lock")
l.Unlock()
l.Unlock()
}

  写锁阻止写锁;同样,第二个Lock会阻塞在获取rw.writeSem信号量上,导致两个Lock都无法执行Unlock。

  总之一句话,go中RWLock不支持可重入,不要在同一协程调用多次RWMutex的锁,不管读锁还是写锁。

5. 写锁优先级高于读锁

  有写锁等待时,优先加写锁,因此写锁不至于饿死一直无法获取到锁。

  写操作到来时,会把RWMutex.readerCount值拷贝到RWMutex.readerWait中,用于标记排在写操作前面的读者个数。

  前面的读操作结束后,除了会递减RWMutex.readerCount,还会递减RWMutex.readerWait值,当RWMutex.readerWait值变为0时唤醒写操作。

参考资料

go中的sync.RWMutex源码解读

Go 并发实战 -- sync RWMutex

golang sync.RWMutex总结笔记的更多相关文章

  1. golang sync.RWMutex

    sync.RWMutex package main import ( "fmt" "runtime" "sync" ) func click ...

  2. golang中sync.RWMutex和sync.Mutex区别

    golang中sync包实现了两种锁Mutex (互斥锁)和RWMutex(读写锁),其中RWMutex是基于Mutex实现的,只读锁的实现使用类似引用计数器的功能. type Mutex     f ...

  3. golang实现分布式缓存笔记(一)基于http的缓存服务

    目录 前言 cache 缓存服务接口 cache包实现 golang http包使用介绍 hello.go Redirect.go http-cache-server 实现 cacheHandler ...

  4. golang sync包

    sync 在golang 文档上,golang不希望通过共享内存来进行进程间的协同操作,而是通过channel的方式来进行,当然,golang也提供了共享内存,锁等机制进行协同操作的包: 互斥锁: M ...

  5. golang sync.Cond条件变量的使用

    cond.Wait()的操作实际上是对与cond绑定的锁先进行解锁,在等待通知:接收到通知后,会尝试加锁,加锁成功则唤醒否则继续等待通知: cond.Waite()前必须对关连锁加锁,否则panic ...

  6. Golang sync

    Go1.9.2 sync库里包含下面几类:Mutex/RWMutex/Cond/WaitGroup/Once/Map/Pool 1.Mutex:互斥锁,等同于linux下的pthread_mutex_ ...

  7. Go 互斥锁(sync.Mutex)和 读写锁(sync.RWMutex)

    什么时候需要用到锁? 当程序中就一个线程的时候,是不需要加锁的,但是通常实际的代码不会只是单线程,所以这个时候就需要用到锁了,那么关于锁的使用场景主要涉及到哪些呢? 多个线程在读相同的数据时 多个线程 ...

  8. Go语言协程并发---读写锁sync.RWMutex

    package main import ( "fmt" "sync" "time" ) /* 读写锁 多路只读 一路只写 读写互斥 */ / ...

  9. Golang Sync.WaitGroup 使用及原理

    Golang Sync.WaitGroup 使用及原理 使用 func main() { var wg sync.WaitGroup for i := 0; i < 10; i++ { wg.A ...

  10. go源码阅读 - sync/rwmutex

    相比于Mutex来说,RWMutex锁的粒度更细,使用RWMutex可以并发读,但是不能并发读写,或者写写. 1. sync.RWMutex的结构 type RWMutex struct { // 互 ...

随机推荐

  1. 互联网公司IT系统架构进化之路

    一日,与一高手在茶馆聊天.他问道:在鞋厂剑派这两年,可习的什么高深的剑法?我不由一愣,细细想来,这两年每日练习的都是简单的劈砍动作和一些简练的套路.并没有去练什么高深的剑法.不过鞋厂剑派在江湖上也算小 ...

  2. mybatis bind 标签 覆盖 复杂对象的某个属性值 问题。

    需求: 有四个sql 都需要用一个 相同的where 条件,于是定义了一个sql 标签. 然后在每个sql中使用 <include refid="myWhereSql"> ...

  3. 【转载】win10怎么设置窗口护眼?

    转载地址:http://www.downza.cn/xy/122656.html win10设置窗口护眼模式的方法 2021-04-13 13:30:54来源:下载之家作者:down 小伙伴们你们知道 ...

  4. 2022-04-18内部群每日三题-清辉PMP

    1.在为一个有预算限制的项目生成状态报告时,项目经理发现该项目比进度计划落后一周.若要将项目拉回正轨,项目经理应该怎么做? A.重新分配关键路径活动的团队成员. B.向项目发起人要求额外的时间. C. ...

  5. Ubuntu20.04 TLS 开机卡在“A start job is running for wait for network to be Configured”解决

    问题: 安装ubuntu20.04 TLS系统后,开机卡在"A start job is running for wait for network to be Configured" ...

  6. fail-fast简介

    转载请注明出处:http://www.cnblogs.com/skywang12345/p/3308762.html fail-fast简介(使用concurrentHashMap可以完美避免这个问题 ...

  7. 查看linux进程启动运行时间

    ps -eo pid,tty,user,lstart,etime,cmd|grep nginx 参数说明: pid:进程ID tty:终端 user:用户 lstart:开始时间 etime:运行时间 ...

  8. OSI网络七层模型简明教程

    如果你读过计算机专业,或者学习过网络通信,那你一定听说过 OSI 模型,它曾无数次让你头大.OSI 是 Open System Interconnection 的缩写,译为"开放式系统互联& ...

  9. 杭电OJ--1003题C++实现

    #include<iostream>using namespace std;int a[100000];void solve(int k,int n,int t);int main(){  ...

  10. mysql datetime和timestamp区别

    datetime: 保存格式为YYYYMMDDHHMMSS的整数,与时区无关,存入什么值就是什么值,不会根据当前时区进行转换.mysql5.6.4中可以存储小数片段,最多小数点后6位,在mysql5. ...