前言:
上一篇博文講述了pstack的使用和原理. 和jstack一樣, pstack能獲取進程的線程堆棧快照, 方便檢驗和性能評估. 但jstack功能更加的強大, 它能對潛在的死鎖予以提示, 而pstack只提供了線索, 需要gdb進一步的確定.
那Linux下, 如何去檢測死鎖, 如何讓死鎖的檢測能夠更加的智能和方便? 這是本文的核心主旨, 讓我們一同分享下思路.
常規做法:
我們來模擬一個出現死鎖的程序, 然后通過常規方式來確定是否出現了死鎖, 以及在那些線程上出現的.
如下是經典的死鎖程序:
注: 線程A獲取鎖1, 線程B獲取鎖2, 然后線程A/B分別去獲取鎖2/1, 兩者誰也不松手的, 又不得對方的, 於是duang, duang duang...
使用pstack來快速掃描堆棧:
發現有兩個線程均在lock中等待, 存在死鎖的嫌疑, 需要gdb后具體確認.
圖文解讀: 線程10800申請mutex_1(此時被線程10799所有), 而線程10799申請mutex_2(被線程10800所有), 於是線程10800在等待線程10799的釋放, 而線程10799在等待線程10800的釋放, 於是我們可以確定發生死鎖了.
但這種方式, 需要開發人員自己去驗證和排除, 復雜的案例就並不輕松了.
在gdb中, 我們可以只能看到mutex對應的線程, 卻無法反向獲取到線程擁有的mutex列表, 如果有這個信息, 就像jstack工具那樣, 獲取對死鎖的判定, 只要掃下堆棧信息, 就能基本的判定了.
檢測模型:
對於死鎖, 操作系統課程中, 着重講述了其常規模型/檢測算法/規避建議, 這邊不再展開.
一言以蔽之: 死鎖的發生, 必然意味着有向圖(依賴關系)的構建存在環.
關於檢測模型, 我們可以這么假定, 鎖為有向邊, 申請鎖的線程A為起點, 擁有鎖的線程B為終點. 這樣就形成線程A到線程B的一條有向邊. 而眾多的鎖(邊)和線程(點), 就構成了一個有向圖.
於是乎, 一個死鎖檢測的算法, 就轉變為圖論中有向圖的環判斷問題. 而該問題, 可以借助成熟的拓撲遍歷算法輕易實現.
//拓撲排序:可以測試一個有向圖是否有環 void Graph::topsort( ) { Queue<Vertex> q; int counter = 0; q.makeEmpty( ); for each Vertex v if( v.indegree == 0 ) q.enqueue( v ); while( !q.isEmpty( ) ) { Vertex v = q.dequeue( ); counter++; for each Vertex w adjacent to v if( --w.indegree == 0 ) q.enqueue( w ); } if( counter != NUM_VERTICES ) throw CycleFoundException( ); }
解決方案:
檢測模型的確定, 讓人豁然開朗. 但如何落地實現, 成了另一個攔路虎.
讓我們從反向獲取線程擁有的鎖列表這個思路出發, 如何去實現? 如果我們能像java反射一樣, 攔截lock/unlock操作, 並添加匯報線程與鎖關系的功能, 那自然能構建有向圖. 進而實現自動檢測死鎖情況.
但是C/C++沒有反射, 不過可以在所有的lock/unlock代碼處添加樁代碼, 並得以解決. 但這對使用方的代碼具有侵入性, 能否改善呢?
上天總是眷顧勤奮的人, 這邊我們可以借助宏擴展(宏不會遞歸展開, 這是關鍵)來巧妙實現這個功能.
#include <sys/syscall.h> #define gettid() syscall(__NR_gettid) // 攔截lock, 添加before, after操作, 記錄鎖與線程的關系 #define pthread_mutex_lock(x) \ do { \ printf("before=>thread_id:%d apply mutex:%p\n", gettid(), x); \ pthread_mutex_lock(x); \ printf("after=>thread_id:%d acquire mutex:%p\n", gettid(), x); \ } while (false); // 攔截unlock, 添加after操作, 解除鎖和線程的關系 #define pthread_mutex_unlock(x) \ do { \ pthread_mutex_unlock(x); \ printf("unlock=>thread_id: %d release mutex:%p\n", gettid(), x); \ } while(false);
注: gettid函數用於獲取線程實際的id, 重名名的pthread_mutex_lock/pthread_mutex_unlock宏, 添加了對before/after攔截調用, 並匯報記錄了鎖與線程的關系.
我們可以對before/after操作, 進行實際的圖構建和檢測. 而且該宏替換, 輕松解決了代碼侵入性的問題.
讓我們在回憶jstack的使用, 猜測java就是借助反射, 輕松實現了類似的功能, 使得其能檢測死鎖情況.
檢驗效果:
有了上述的理論基礎和思路后, 進行嘗試和擴展.
這邊寫了一個簡單的檢測工具庫, 使用非常的簡單.
在需要檢測的代碼中, 引入dead_lock_stub.h頭文件, 然后在main函數的開頭加入
DeadLockGraphic::getInstance().start_check();
實驗效果如下:
樣例代碼的網盤地址如下: http://pan.baidu.com/s/1ntzHEeX
寫在最后:
如果你覺得這篇文章對你有幫助, 請小小打賞下. 其實我想試試, 看看寫博客能否給自己帶來一點小小的收益. 無論多少, 都是對樓主一種由衷的肯定.