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

死鎖產(chǎn)生的四個(gè)必要條件,也就是死鎖產(chǎn)生的原因如下,缺一不可:
| 條件 | 說明 |
| 互斥條件 | 資源一次只能被一個(gè)線程占用 |
| 持有且等待 | 線程持有資源同時(shí)請(qǐng)求新資源 |
| 不可搶占 | 資源不能被強(qiáng)制釋放 |
| 循環(huán)等待 | 形成線程-資源的循環(huán)鏈 |
這四個(gè)必要條件,只要打破一個(gè),就不會(huì)形成死鎖,但一般來說我們不會(huì)去打破第一個(gè)互斥條件,因?yàn)檫@一般是資源自帶的性質(zhì),我們無法避免。比如說買票時(shí)的車票數(shù),不同人看到的剩余票數(shù)應(yīng)該是一致的,這無法避免。
而要打破死鎖,首先需要的是檢測(cè)到死鎖。那么如何檢測(cè)呢?回到剛才的圖我們可以發(fā)現(xiàn),形成死鎖后圖中出現(xiàn)了環(huán),也就是說我們可以將線程與資源占用關(guān)系抽象成圖之后,檢測(cè)圖中是否形成環(huán)回路,只要有環(huán),那就出現(xiàn)了死鎖,進(jìn)而采取下一步操作。
二、實(shí)現(xiàn)部分
我們?cè)诖藘H實(shí)現(xiàn)一個(gè)簡(jiǎn)易化的版本,由于理解死鎖檢測(cè)。
1. 數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)
核心數(shù)據(jù)結(jié)構(gòu)
struct source_type {
uint64 id; // 線程ID或鎖地址
enum Type type; // 類型:PROCESS 或 RESOURCE(雖然代碼中只用到了PROCESS)
uint64 lock_id; // 鎖ID(用于locklist)
int degress; // 鎖的等待計(jì)數(shù)
};
struct vertex {
struct source_type s; // 頂點(diǎn)數(shù)據(jù)
struct vertex *next; // 鄰接表指針
};任務(wù)圖(等待圖)
struct task_graph {
struct vertex list[MAX]; // 頂點(diǎn)數(shù)組(鄰接表頭)
int num; // 頂點(diǎn)數(shù)量
struct source_type locklist[MAX]; // 鎖持有表
int lockidx; // 鎖數(shù)量
pthread_mutex_t mutex; // 保護(hù)圖結(jié)構(gòu)的鎖(實(shí)際未使用)
};- 鄰接表:list[MAX] 存儲(chǔ)所有線程頂點(diǎn),next 指向該線程等待的其他線程
- 鎖持有表:記錄每個(gè)鎖當(dāng)前被哪個(gè)線程持有
2. 核心邏輯
核心規(guī)則
代碼的邏輯是當(dāng)線程1想要持有鎖時(shí),先查詢鎖持有表,如果鎖沒有被占用,那就直接使用鎖并在鎖持有表中新增一條對(duì)應(yīng)的記錄。如果鎖被占用了,就在圖中連一條指向占有線程2的邊。
當(dāng)線程T1試圖獲取已被T2持有的鎖L時(shí):
添加邊:T1 → T2
表示T1在等待T2釋放鎖三個(gè)關(guān)鍵函數(shù)(部分偽代碼)
// 1. 加鎖前:如果鎖已被其他線程持有,建立等待關(guān)系
void lock_before(tid, lockaddr) {
if (鎖已被其他線程T2持有) {
添加邊:當(dāng)前線程T1 → T2
lock.degress++ // 等待計(jì)數(shù)增加
}
}
// 2. 加鎖后:更新鎖的持有者
void lock_after(tid, lockaddr) {
if (鎖是空閑的) {
記錄當(dāng)前線程持有該鎖
} else {
移除之前建立的等待邊(因?yàn)橐呀?jīng)獲得鎖)
更新鎖的持有者為當(dāng)前線程
}
}
// 3. 解鎖后:如果沒人等待,清空鎖記錄
void unlock_after(tid, lockaddr) {
if (鎖的degress == 0) {
清空鎖的持有信息
}
}3. 死鎖檢測(cè)算法
在以上接口的基礎(chǔ)上,我們?cè)偬砑右粋€(gè)檢測(cè)圖中環(huán)的算法就能實(shí)現(xiàn)死鎖檢測(cè)。最暴力的做法是使用DFS 但不推薦。推薦使用 Tarjan 算法來檢測(cè)環(huán),一個(gè)環(huán)一定是一個(gè)有向圖的一個(gè)強(qiáng)連通分量,通過這個(gè)性質(zhì)來實(shí)現(xiàn)死鎖檢測(cè)。
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");
}以上是死鎖檢測(cè)的一個(gè)基本思路,將線程與資源及其關(guān)系抽象成有向圖,對(duì)圖進(jìn)行環(huán)回路檢測(cè)。而在使用死鎖檢測(cè)時(shí),可以用一個(gè)獨(dú)立的線程監(jiān)控,不影響主程序性能。
到此這篇關(guān)于C++ 死鎖檢測(cè)基礎(chǔ)思路的文章就介紹到這了,更多相關(guān)C++ 死鎖檢測(cè)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語言實(shí)現(xiàn)會(huì)員管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C語言實(shí)現(xiàn)會(huì)員管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03
C++實(shí)現(xiàn)簡(jiǎn)單酒店管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)簡(jiǎn)單酒店管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-08-08
C++實(shí)現(xiàn)百度坐標(biāo)(BD09)及GCJ02與WGS84之間的轉(zhuǎn)換
這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)百度坐標(biāo)(BD09)及GCJ02與WGS84之間的轉(zhuǎn)換的方法,文中的示例代碼講解詳細(xì),希望對(duì)大家有所幫助2023-03-03
C語言通過二分查找實(shí)現(xiàn)猜數(shù)字游戲
這篇文章主要為大家詳細(xì)介紹了在C語言中如何通過二分查找思想編寫一個(gè)簡(jiǎn)單的猜數(shù)字游戲,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-02-02
Matlab實(shí)現(xiàn)好看的配對(duì)箱線圖的繪制
配對(duì)箱線圖,常見于配對(duì)樣本的數(shù)據(jù)分析中,它除了能夠表現(xiàn)兩組的整體差異,還能夠清晰地呈現(xiàn)單個(gè)樣本的前后改變。本文將用Matlab實(shí)現(xiàn)配對(duì)箱線圖的繪制,需要的可以參考一下2022-08-08
C++實(shí)現(xiàn)銀行排隊(duì)系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)銀行排隊(duì)系統(tǒng),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07
c語言同名標(biāo)靶點(diǎn)自動(dòng)匹配算法實(shí)現(xiàn)實(shí)例代碼
這篇文章主要介紹了c語言同名標(biāo)靶點(diǎn)自動(dòng)匹配算法實(shí)現(xiàn)實(shí)例代碼,分享了相關(guān)代碼示例,小編覺得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-02-02

