zoukankan      html  css  js  c++  java
  • golang sync.RWMutex总结笔记

    背景

    最近项目中遇到两次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

  • 相关阅读:
    js简单验证码的生成和验证
    基本够用的php.ini配置文件(CentOS7)
    转发:CentOS下tar压缩排除某个文件夹或文件及解压
    阿里云服务器CentOS7 vsftp安装、设置及后台端口的设置
    转发:entos7修改文件夹权限和用户名用户组
    转发:查看centos中的用户和用户组
    阿里云服务器CentOS7怎么分区格式化/挂载硬盘
    php调试用的几个小方法
    Jquery实现日期转换为 Unix时间戳及时间戳转换日期
    Jquery计算时间戳之间的差值,可返回年,月,日,小时等
  • 原文地址:https://www.cnblogs.com/luoming1224/p/14666173.html
Copyright © 2011-2022 走看看