Golang中如何避免死鎖:加鎖
- 讀寫鎖中的可讀鎖(
sync.RWMutex
的RLock()
)可以嵌套使用的。 - 互斥鎖(
sync.Mutex
和sync.RWMutex
的Lock()
)是不可以互相嵌套的,且不可以與可讀鎖嵌套。
之前我在讀寫鎖和互斥鎖上理解有偏差,認為讀寫鎖與互斥鎖是完全獨立且相互對應的關系。現在理解為 互斥
只是一種特性。而把 sync.Mutex
叫作 全局鎖, sync.RWMutex
叫作 讀寫鎖。
全局鎖 sync.Mutex
,是同一時刻某一資源只能上一個鎖,此鎖具有排他性,上鎖后只能被此線程使用,直至解鎖。加鎖后即不能讀也不能寫。全局鎖是互斥鎖,即 sync.Mutex
是個互斥鎖。
讀寫鎖 sync.RWMutex
,將使用者分為讀者和寫者兩個概念,支持同時多個讀者一起讀共享資源,但寫時只能有一個,並且在寫時不可以讀。理論上來說,sync.RWMutex
的 Lock()
也是個互斥鎖。
踩坑點
將上面的結論展開一下,更清晰得說(為避免理解偏差寧可嘮叨一些):
sync.Mutex
的鎖是不可以嵌套使用的。sync.RWMutex
的mu.Lock()
是不可以嵌套的。sync.RWMutex
的mu.Lock()
中不可以嵌套mu.RLock()
。(這是個注意的地方)
否則,會 panic fatal error: all goroutines are asleep - deadlock!
所以以下函數不會造成 panic:
var l sync.RWMutex func readAndRead() { // 可讀鎖內使用可讀鎖 l.RLock() defer l.RUnlock() l.RLock() defer l.RUnlock() } func main() { lockAndRead() time.Sleep(5 * time.Second) }
而將 readAndRead
換為以下三種函數均會造成 panic:
func lockAndLock() { // 全局鎖內使用全局鎖 l.Lock() defer l.Unlock() l.Lock() defer l.Unlock() } func lockAndRead() { // 全局鎖內使用可讀鎖 l.Lock() defer l.Unlock() // 由於 defer 是棧式執行,所以這兩個鎖是嵌套結構 l.RLock() defer l.RUnlock() } func readAndLock() { // 可讀鎖內使用全局鎖 l.RLock() defer l.RUnlock() l.Lock() defer l.Unlock() }
注: 在 goroutine 中的 panic 不會影響主程序,所以在測試時要注意並不是沒有 panic 輸出就一定是沒發生。