• 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

  • 相关阅读:
    使用 Web 服务 为 ECS Linux 实例配置网站及绑定域名
    部署 LAMP
    PHP与MYSQL事务处理
    centos6 yum 安装nginx 不成功解决办法
    5.5版本以上”No input file specified“问题解决
    php访问url的四种方式
    mysql索引总结----mysql 索引类型以及创建
    承诺,项目管理中的大杀器
    什么叫工作到位?
    Java中注解Annotation的定义、使用、解析
  • 原文地址:https://www.cnblogs.com/luoming1224/p/14666173.html
Copyright © 2020-2023  润新知