golang RWMutex RLock重入導致死鎖


現象

一個組件實現了raft分布式協議,在分布式部署環境中來進行選主,在某客戶現場突然發生文件句柄泄露,在打印某些錯誤日志后,幾個小時內沒有日志打印,然后某個協程突然報無可用的文件句柄。

分析

經過代碼和日志分析,組件正常每分鍾會打印所有部署節點的日志信息,沒有打印日志說明定時器處理邏輯for...select里面某個函數邏輯卡住了,然后發生文件句柄泄露,經過梳理是在響應心跳的邏輯沒有回,導致一直創建協程。心跳響應邏輯和定時器處理邏輯中有用到同一個鎖,初步判斷為這個鎖發生死鎖。

在本地環境復現了后,通過debug/pprof分析,確實有四處在等待該鎖,兩處等待寫鎖,兩處等待讀鎖,但是代碼看起來都很正常;pprof分析也沒有提示死鎖。然后通過搜索引擎搜索關鍵詞“RWMutex 死鎖”,找到一篇文件說RWMutex RLock重入可能導致死鎖,如果網絡異常,有分布式節點疑似下線時,代碼中確實有一處會有該鎖的RLock同一協程兩次重入調用。

RLock重入死鎖復現

 1 func TestDeadLock(t *testing.T) {
 2     var l sync.RWMutex
 3     var wg sync.WaitGroup
 4     wg.Add(2)
 5 
 6     c := make(chan int)
 7     go func() {
 8         defer wg.Done()
 9 
10         l.RLock()
11         defer l.RUnlock()
12         t.Log("acquire RLock first")
13 
14         c <- 1
15         runtime.Gosched()
16 
17         t.Log("wait readLock")
18         l.RLock()
19         defer l.RUnlock()
20         t.Log("acquire RLock second")
21     }()
22 
23     go func() {
24         defer wg.Done()
25 
26         <-c
27 
28         t.Log("wait writeLock")
29         l.Lock()
30         defer l.Unlock()
31         t.Log("acquire Lock")
32     }()
33 
34     wg.Wait()
35     t.Log("test finish")
36 }

通過以上測試代碼,很容易復現該死鎖現象,而在java中可重入讀寫鎖讀鎖重入不會導致死鎖,所以剛開始看到RLock重入時也沒有想到該問題。

源碼分析

 

參考文檔

golang RWMutex RLock重入導致死鎖

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM