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 輸出就一定是沒發生。
