linux死鎖檢測的一種思路


前言:
  上一篇博文講述了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

 寫在最后:
  
如果你覺得這篇文章對你有幫助, 請小小打賞下. 其實我想試試, 看看寫博客能否給自己帶來一點小小的收益. 無論多少, 都是對樓主一種由衷的肯定.

  

 

 

 


免責聲明!

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



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