C++ 死鎖檢測基礎(chǔ)思路詳解
一、理論部分
死鎖(Deadlock)是并發(fā)編程中最棘手的問題之一。不同于內(nèi)存泄漏可以通過工具最終定位,死鎖一旦發(fā)生,往往導(dǎo)致系統(tǒng)徹底卡死,且難以復(fù)現(xiàn)。
死鎖的現(xiàn)象舉一個簡單的例子,如下圖所示,3個線程都在運行,且圖中資源均一次只能被一個線程占用,線程A占用資源1,線程B占用資源2,線程C占用資源3,此時線程A不釋放資源1且想去占用資源2,而線程B也不釋放資源2并且想去占用資源3,而線程C同樣不釋放資源3去占用資源1。這樣,線程A, B, C 都因為獲取不到足夠的資源而一直陷入等待狀態(tài)。這種現(xiàn)象就是死鎖。

死鎖產(chǎn)生的四個必要條件,也就是死鎖產(chǎn)生的原因如下,缺一不可:
| 條件 | 說明 |
| 互斥條件 | 資源一次只能被一個線程占用 |
| 持有且等待 | 線程持有資源同時請求新資源 |
| 不可搶占 | 資源不能被強(qiáng)制釋放 |
| 循環(huán)等待 | 形成線程-資源的循環(huán)鏈 |
這四個必要條件,只要打破一個,就不會形成死鎖,但一般來說我們不會去打破第一個互斥條件,因為這一般是資源自帶的性質(zhì),我們無法避免。比如說買票時的車票數(shù),不同人看到的剩余票數(shù)應(yīng)該是一致的,這無法避免。
而要打破死鎖,首先需要的是檢測到死鎖。那么如何檢測呢?回到剛才的圖我們可以發(fā)現(xiàn),形成死鎖后圖中出現(xiàn)了環(huán),也就是說我們可以將線程與資源占用關(guān)系抽象成圖之后,檢測圖中是否形成環(huán)回路,只要有環(huán),那就出現(xiàn)了死鎖,進(jìn)而采取下一步操作。
二、實現(xiàn)部分
我們在此僅實現(xiàn)一個簡易化的版本,由于理解死鎖檢測。
1. 數(shù)據(jù)結(jié)構(gòu)設(shè)計
核心數(shù)據(jù)結(jié)構(gòu)
struct source_type {
uint64 id; // 線程ID或鎖地址
enum Type type; // 類型:PROCESS 或 RESOURCE(雖然代碼中只用到了PROCESS)
uint64 lock_id; // 鎖ID(用于locklist)
int degress; // 鎖的等待計數(shù)
};
struct vertex {
struct source_type s; // 頂點數(shù)據(jù)
struct vertex *next; // 鄰接表指針
};任務(wù)圖(等待圖)
struct task_graph {
struct vertex list[MAX]; // 頂點數(shù)組(鄰接表頭)
int num; // 頂點數(shù)量
struct source_type locklist[MAX]; // 鎖持有表
int lockidx; // 鎖數(shù)量
pthread_mutex_t mutex; // 保護(hù)圖結(jié)構(gòu)的鎖(實際未使用)
};- 鄰接表:list[MAX] 存儲所有線程頂點,next 指向該線程等待的其他線程
- 鎖持有表:記錄每個鎖當(dāng)前被哪個線程持有
2. 核心邏輯
核心規(guī)則
代碼的邏輯是當(dāng)線程1想要持有鎖時,先查詢鎖持有表,如果鎖沒有被占用,那就直接使用鎖并在鎖持有表中新增一條對應(yīng)的記錄。如果鎖被占用了,就在圖中連一條指向占有線程2的邊。
當(dāng)線程T1試圖獲取已被T2持有的鎖L時:
添加邊:T1 → T2
表示T1在等待T2釋放鎖三個關(guān)鍵函數(shù)(部分偽代碼)
// 1. 加鎖前:如果鎖已被其他線程持有,建立等待關(guān)系
void lock_before(tid, lockaddr) {
if (鎖已被其他線程T2持有) {
添加邊:當(dāng)前線程T1 → T2
lock.degress++ // 等待計數(shù)增加
}
}
// 2. 加鎖后:更新鎖的持有者
void lock_after(tid, lockaddr) {
if (鎖是空閑的) {
記錄當(dāng)前線程持有該鎖
} else {
移除之前建立的等待邊(因為已經(jīng)獲得鎖)
更新鎖的持有者為當(dāng)前線程
}
}
// 3. 解鎖后:如果沒人等待,清空鎖記錄
void unlock_after(tid, lockaddr) {
if (鎖的degress == 0) {
清空鎖的持有信息
}
}3. 死鎖檢測算法
在以上接口的基礎(chǔ)上,我們再添加一個檢測圖中環(huán)的算法就能實現(xiàn)死鎖檢測。最暴力的做法是使用DFS 但不推薦。推薦使用 Tarjan 算法來檢測環(huán),一個環(huán)一定是一個有向圖的一個強(qiáng)連通分量,通過這個性質(zhì)來實現(xiàn)死鎖檢測。
4. 鉤子機(jī)制(Hooking)
最后是通過鉤子機(jī)制獲取并修改原始函數(shù)指針,將pthread_mutex_lock和pthread_mutex_unlock改寫邏輯:
// hook
// define
typedef int (*pthread_mutex_lock_t)(pthread_mutex_t *mutex);
pthread_mutex_lock_t pthread_mutex_lock_f = NULL;
typedef int (*pthread_mutex_unlock_t)(pthread_mutex_t *mutex);
pthread_mutex_unlock_t pthread_mutex_unlock_f = NULL;
// implement
int pthread_mutex_lock(pthread_mutex_t *mutex) {
pthread_t selfid = pthread_self();
lock_before((uint64_t)selfid, (uint64_t)mutex);
pthread_mutex_lock_f(mutex);
lock_after((uint64_t)selfid, (uint64_t)mutex);
}
int pthread_mutex_unlock(pthread_mutex_t *mutex) {
pthread_mutex_unlock_f(mutex);
pthread_t selfid = pthread_self();
unlock_after((uint64_t)selfid, (uint64_t)mutex);
}
// init
void init_hook(void) {
if (!pthread_mutex_lock_f)
pthread_mutex_lock_f = dlsym(RTLD_NEXT, "pthread_mutex_lock");
if (!pthread_mutex_unlock_f)
pthread_mutex_unlock_f = dlsym(RTLD_NEXT, "pthread_mutex_unlock");
}以上是死鎖檢測的一個基本思路,將線程與資源及其關(guān)系抽象成有向圖,對圖進(jìn)行環(huán)回路檢測。而在使用死鎖檢測時,可以用一個獨立的線程監(jiān)控,不影響主程序性能。
到此這篇關(guān)于C++ 死鎖檢測基礎(chǔ)思路的文章就介紹到這了,更多相關(guān)C++ 死鎖檢測內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++實現(xiàn)百度坐標(biāo)(BD09)及GCJ02與WGS84之間的轉(zhuǎn)換
這篇文章主要為大家詳細(xì)介紹了C++實現(xiàn)百度坐標(biāo)(BD09)及GCJ02與WGS84之間的轉(zhuǎn)換的方法,文中的示例代碼講解詳細(xì),希望對大家有所幫助2023-03-03
c語言同名標(biāo)靶點自動匹配算法實現(xiàn)實例代碼
這篇文章主要介紹了c語言同名標(biāo)靶點自動匹配算法實現(xiàn)實例代碼,分享了相關(guān)代碼示例,小編覺得還是挺不錯的,具有一定借鑒價值,需要的朋友可以參考下2018-02-02

