导航

golang sync.RWMutex总结笔记

Posted on 2021-04-16 11:25  luoming1224  阅读(329)  评论(0编辑  收藏  举报

背景

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