【C/C++】死鎖的四大條件與預防策略詳解

文章目錄

  • 死鎖的四大條件與預防策略詳解
    • 一、死鎖的產生條件(四個必要條件)
    • 二、代碼示例
    • 三、死鎖的預防手段(以 C/C++ 為例)
      • 1. 破壞“循環等待” —— 統一加鎖順序(推薦)
      • 2. 使用 `std::lock` 一次性加多個鎖
      • 3. 使用 `try_lock` 實現非阻塞獲取資源
      • 4. 超時機制(C++17 起)
      • 5. 添加超時檢測或死鎖檢測邏輯(高級)
    • 四、死鎖的定位手段
      • 1. 代碼審查:查看是否存在資源互相等待邏輯。
      • 2. 日志排查:
      • 3. 使用工具定位
      • 4. 打印鎖地址對應變量
    • 五、小結

死鎖的四大條件與預防策略詳解

死鎖(Deadlock)是指多個線程或進程因相互等待資源而永久阻塞的現象。


一、死鎖的產生條件(四個必要條件)

  1. 互斥條件:資源不能被多個線程同時占用。
  2. 占有且等待:一個線程至少持有一個資源,同時等待其他線程所持有的資源。
  3. 不可剝奪:線程持有的資源在未使用完之前不能被強制剝奪。
  4. 循環等待:存在一個線程等待鏈,鏈中的每個線程都在等待下一個線程所持有的資源。

只要四個條件同時滿足,就可能發生死鎖。


二、代碼示例

#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>// 定義兩個互斥量
std::mutex mutexA;
std::mutex mutexB;void deadlock_version() {std::thread t1([] {std::lock_guard<std::mutex> lockA(mutexA);std::cout << "[T1] Locked mutexA\n";std::this_thread::sleep_for(std::chrono::milliseconds(100));std::lock_guard<std::mutex> lockB(mutexB);  // 等待 mutexBstd::cout << "[T1] Locked mutexB\n";});std::thread t2([] {std::lock_guard<std::mutex> lockB(mutexB);std::cout << "[T2] Locked mutexB\n";std::this_thread::sleep_for(std::chrono::milliseconds(100));std::lock_guard<std::mutex> lockA(mutexA);  // 等待 mutexAstd::cout << "[T2] Locked mutexA\n";});t1.join();t2.join();
}void no_deadlock_version() {std::thread t1([] {std::unique_lock<std::mutex> lockA(mutexA, std::defer_lock);std::unique_lock<std::mutex> lockB(mutexB, std::defer_lock);std::lock(lockA, lockB);  // 同時加鎖,避免死鎖std::cout << "[T1] Locked mutexA and mutexB safely\n";});std::thread t2([] {std::unique_lock<std::mutex> lockA(mutexA, std::defer_lock);std::unique_lock<std::mutex> lockB(mutexB, std::defer_lock);std::lock(lockA, lockB);  // 同樣順序加鎖std::cout << "[T2] Locked mutexA and mutexB safely\n";});t1.join();t2.join();
}int main() {std::cout << "1. 死鎖版本開始:\n";deadlock_version();// 給死鎖版本 3 秒時間卡住(演示用)// std::this_thread::sleep_for(std::chrono::seconds(3));// std::cout << "\n2. 無死鎖版本開始:\n";// no_deadlock_version();deadlock_thread.join(); // 等待死鎖線程(實際上它可能永遠卡住)return 0;
}

🧪 輸出示例:

1. 死鎖版本開始:
[T1] Locked mutexA
[T2] Locked mutexB
(此時死鎖,程序卡住)2. 無死鎖版本開始:
[T1] Locked mutexA and mutexB safely
[T2] Locked mutexA and mutexB safely

🛠 如何調試死鎖(gdb 示例)

g++ -g deadlock_example.cpp -o deadlock_example -pthread
gdb ./deadlock_example
(gdb) run
(程序卡住時)<ctrl + c>
(gdb) thread apply all bt
(gdb) run
Starting program: /.../a.out 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
1. 死鎖版本開始:
[New Thread 0x7ffff77ff6c0 (LWP 414762)]
[T1] Locked mutexA
[New Thread 0x7ffff6ffe6c0 (LWP 414763)]
[T2] Locked mutexB
^C
Thread 1 "a.out" received signal SIGINT, Interrupt.
Download failed: Invalid argument.  Continuing without source file ./nptl/./nptl/futex-internal.c.
0x00007ffff7898d71 in __futex_abstimed_wait_common64 (private=128, cancel=true, abstime=0x0, op=265, expected=414762, futex_word=0x7ffff77ff990) at ./nptl/futex-internal.c:57
warning: 57     ./nptl/futex-internal.c: No such file or directory
(gdb) info threadsId   Target Id                                  Frame 
* 1    Thread 0x7ffff7e8c740 (LWP 414759) "a.out" 0x00007ffff7898d71 in __futex_abstimed_wait_common64 (private=128, cancel=true, abstime=0x0, op=265, expected=414762, futex_word=0x7ffff77ff990) at ./nptl/futex-internal.c:572    Thread 0x7ffff77ff6c0 (LWP 414762) "a.out" futex_wait (private=0, expected=2, futex_word=0x55555555b1a0 <mutex>) at ../sysdeps/nptl/futex-internal.h:1463    Thread 0x7ffff6ffe6c0 (LWP 414763) "a.out" futex_wait (private=0, expected=2, futex_word=0x55555555b160 <mutexA>) at ../sysdeps/nptl/futex-internal.h:146(gdb) thread apply all btThread 3 (Thread 0x7ffff6ffe6c0 (LWP 414763) "a.out"):
#0  futex_wait (private=0, expected=2, futex_word=0x55555555b160 <mutexA>) at ../sysdeps/nptl/futex-internal.h:146
#1  __GI___lll_lock_wait (futex=futex@entry=0x55555555b160 <mutexA>, private=0) at ./nptl/lowlevellock.c:49
#2  0x00007ffff78a0101 in lll_mutex_lock_optimized (mutex=0x55555555b160 <mutexA>) at ./nptl/pthread_mutex_lock.c:48
#3  ___pthread_mutex_lock (mutex=0x55555555b160 <mutexA>) at ./nptl/pthread_mutex_lock.c:93
#4  0x000055555555680b in __gthread_mutex_lock(pthread_mutex_t*) ()
#5  0x00005555555569be in std::mutex::lock() ()
#6  0x0000555555556a5c in std::lock_guard<std::mutex>::lock_guard(std::mutex&) ()
#7  0x00005555555554b9 in deadlock_version()::{lambda()#2}::operator()() const ()
#8  0x0000555555556705 in void std::__invoke_impl<void, deadlock_version()::{lambda()#2}>(std::__invoke_other, deadlock_version()::{lambda()#2}&&) ()
#9  0x00005555555565fb in std::__invoke_result<deadlock_version()::{lambda()#2}>::type std::__invoke<deadlock_version()::{lambda()#2}>(deadlock_version()::{lambda()#2}&&) ()
#10 0x00005555555564d6 in void std::thread::_Invoker<std::tuple<deadlock_version()::{lambda()#2}> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) ()
#11 0x0000555555556436 in std::thread::_Invoker<std::tuple<deadlock_version()::{lambda()#2}> >::operator()() ()
#12 0x00005555555563be in std::thread::_State_impl<std::thread::_Invoker<std::tuple<deadlock_version()::{lambda()#2}> > >::_M_run() ()
#13 0x00007ffff7cecdb4 in ?? () from /lib/x86_64-linux-gnu/libstdc++.so.6
#14 0x00007ffff789caa4 in start_thread (arg=<optimized out>) at ./nptl/pthread_create.c:447
#15 0x00007ffff7929c3c in clone3 () at ../sysdeps/unix/sysv/linux/x86_64/clone3.S:78Thread 2 (Thread 0x7ffff77ff6c0 (LWP 414762) "a.out"):
#0  futex_wait (private=0, expected=2, futex_word=0x55555555b1a0 <mutex>) at ../sysdeps/nptl/futex-internal.h:146
#1  __GI___lll_lock_wait (futex=futex@entry=0x55555555b1a0 <mutex>, private=0) at ./nptl/lowlevellock.c:49
#2  0x00007ffff78a0101 in lll_mutex_lock_optimized (mutex=0x55555555b1a0 <mutex>) at ./nptl/pthread_mutex_lock.c:48
#3  ___pthread_mutex_lock (mutex=0x55555555b1a0 <mutex>) at ./nptl/pthread_mutex_lock.c:93
#4  0x000055555555680b in __gthread_mutex_lock(pthread_mutex_t*) ()
#5  0x00005555555569be in std::mutex::lock() ()
#6  0x0000555555556a5c in std::lock_guard<std::mutex>::lock_guard(std::mutex&) ()
#7  0x000055555555539d in deadlock_version()::{lambda()#1}::operator()() const ()
--Type <RET> for more, q to quit, c to continue without paging-- 
#8  0x0000555555556742 in void std::__invoke_impl<void, deadlock_version()::{lambda()#1}>(std::__invoke_other, deadlock_version()::{lambda()#1}&&) ()
#9  0x000055555555664e in std::__invoke_result<deadlock_version()::{lambda()#1}>::type std::__invoke<deadlock_version()::{lambda()#1}>(deadlock_version()::{lambda()#1}&&) ()
#10 0x0000555555556502 in void std::thread::_Invoker<std::tuple<deadlock_version()::{lambda()#1}> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) ()
#11 0x0000555555556452 in std::thread::_Invoker<std::tuple<deadlock_version()::{lambda()#1}> >::operator()() ()
#12 0x00005555555563e2 in std::thread::_State_impl<std::thread::_Invoker<std::tuple<deadlock_version()::{lambda()#1}> > >::_M_run() ()
#13 0x00007ffff7cecdb4 in ?? () from /lib/x86_64-linux-gnu/libstdc++.so.6
#14 0x00007ffff789caa4 in start_thread (arg=<optimized out>) at ./nptl/pthread_create.c:447
#15 0x00007ffff7929c3c in clone3 () at ../sysdeps/unix/sysv/linux/x86_64/clone3.S:78Thread 1 (Thread 0x7ffff7e8c740 (LWP 414759) "a.out"):
#0  0x00007ffff7898d71 in __futex_abstimed_wait_common64 (private=128, cancel=true, abstime=0x0, op=265, expected=414762, futex_word=0x7ffff77ff990) at ./nptl/futex-internal.c:57
#1  __futex_abstimed_wait_common (cancel=true, private=128, abstime=0x0, clockid=0, expected=414762, futex_word=0x7ffff77ff990) at ./nptl/futex-internal.c:87
#2  __GI___futex_abstimed_wait_cancelable64 (futex_word=futex_word@entry=0x7ffff77ff990, expected=414762, clockid=clockid@entry=0, abstime=abstime@entry=0x0, private=private@entry=128) at ./nptl/futex-internal.c:139
#3  0x00007ffff789e7a3 in __pthread_clockjoin_ex (threadid=140737345746624, thread_return=0x0, clockid=0, abstime=0x0, block=<optimized out>) at ./nptl/pthread_join_common.c:102
#4  0x00007ffff7cece33 in std::thread::join() () from /lib/x86_64-linux-gnu/libstdc++.so.6
#5  0x000055555555559c in deadlock_version() ()
#6  0x00005555555558ee in main ()

會看到兩個線程分別卡在獲取 mutexB 和 mutexA 的地方,形成典型的死鎖等待鏈。

🔍 解讀 GDB 棧:

🧵 Thread 2

#6 std::lock_guard<std::mutex>::lock_guard(std::mutex&) ()
#7 deadlock_version()::{lambda()#1}::operator()() const ()

線程 2 正在嘗試加鎖 mutex

futex_word=0x55555555b1a0 <mutex>

說明線程 2 被阻塞在 mutex 上。


🧵 Thread 3

#6 std::lock_guard<std::mutex>::lock_guard(std::mutex&) ()
#7 deadlock_version()::{lambda()#2}::operator()() const ()

線程 3 正在嘗試加鎖 mutexA

futex_word=0x55555555b160 <mutexA>

說明線程 3 被阻塞在 mutexA 上。


🧵 Thread 1(主線程)

#4 std::thread::join()
#5 deadlock_version()

主線程正在等待 thread 2 和 thread 3 退出,因此處于阻塞中。

通過 gdb thread apply all bt 看到:

  • 兩個線程分別阻塞在 mutexmutexA 上。
  • 推測這兩個鎖是對方線程正在持有的。
  • 主線程卡在 join(),等待死鎖線程結束。
  • 完全滿足“循環等待”的死鎖條件。

三、死鎖的預防手段(以 C/C++ 為例)

1. 破壞“循環等待” —— 統一加鎖順序(推薦)

  • 所有線程按固定順序加鎖,比如先加 mutex 再加 mutexA
std::mutex m1, m2;void threadA() {std::lock_guard<std::mutex> lock1(m1);std::this_thread::sleep_for(std::chrono::milliseconds(100));std::lock_guard<std::mutex> lock2(m2); // 始終按照 m1->m2 加鎖順序// 臨界區
}void threadB() {std::lock_guard<std::mutex> lock1(m1); // 避免 m2->m1 加鎖順序std::this_thread::sleep_for(std::chrono::milliseconds(100));std::lock_guard<std::mutex> lock2(m2);
}

2. 使用 std::lock 一次性加多個鎖

void safe_function() {std::unique_lock<std::mutex> lock1(m1, std::defer_lock);std::unique_lock<std::mutex> lock2(m2, std::defer_lock);std::lock(lock1, lock2); // 避免死鎖
}

使用 std::lock(mutex1, mutex2) + std::lock_guard

std::lock(mutex, mutexA);
std::lock_guard<std::mutex> lk1(mutex, std::adopt_lock);
std::lock_guard<std::mutex> lk2(mutexA, std::adopt_lock);

3. 使用 try_lock 實現非阻塞獲取資源

void try_lock_example() {if (m1.try_lock()) {if (m2.try_lock()) {// 成功獲取兩個鎖m2.unlock();}m1.unlock();}
}

4. 超時機制(C++17 起)

std::timed_mutex tmutex;void timed_lock_example() {if (tmutex.try_lock_for(std::chrono::milliseconds(100))) {// 成功獲得鎖tmutex.unlock();} else {// 獲取失敗,避免死鎖}
}

5. 添加超時檢測或死鎖檢測邏輯(高級)

待補充~


四、死鎖的定位手段

1. 代碼審查:查看是否存在資源互相等待邏輯。

2. 日志排查:

  • 添加鎖操作日志(獲取、釋放時間、線程ID)。
  • 利用 RAII 包裝鎖并記錄日志。
struct LockLogger {std::mutex& m;std::string name;LockLogger(std::mutex& mutex, const std::string& n) : m(m), name(n) {std::cout << "Locking " << name << std::endl;m.lock();}~LockLogger() {std::cout << "Unlocking " << name << std::endl;m.unlock();}
};

3. 使用工具定位

  • gdb + thread apply all bt:查看線程調用棧,分析是否互相等待。
  • pstack <pid>:打印所有線程棧。
  • strace -p <pid>:查看系統調用是否卡在 futex(用戶態互斥)上。
  • valgrindhelgrind 模塊:檢測數據競爭和死鎖。
valgrind --tool=helgrind ./your_program

4. 打印鎖地址對應變量

std::cout << &mutex << std::endl;
std::cout << &mutexA << std::endl;

運行時查看地址是否和 GDB 中 0x55555555b1a00x55555555b160 一致,可進一步確認鎖對象。

五、小結

內容說明
死鎖四條件互斥、占有且等待、不可剝奪、循環等待
常見預防方式固定加鎖順序、std::locktry_locktimed_mutex
定位手段日志追蹤、gdb/pstack/valgrind、調試器分析調用棧

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/82608.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/82608.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/82608.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Rust編程環境安裝

文章目錄 Rust編程環境安裝一、安裝準備二、安裝步驟對于Linux/macOS用戶對于Windows用戶 三、驗證安裝四、環境配置檢查五、工具鏈管理六、附加功能七、常見問題處理八、編輯器支持九、其他 Rust編程環境安裝 一、安裝準備 1. 支持系統&#xff1a;Windows/Linux/macOS 2. 所…

OpenHarmony平臺驅動使用(五),HDMI

OpenHarmony平臺驅動使用&#xff08;五&#xff09; HDMI 概述 功能簡介 HDMI&#xff08;High Definition Multimedia Interface&#xff09;&#xff0c;即高清多媒體接口&#xff0c;主要用于DVD、機頂盒等音視頻Source到TV、顯示器等Sink設備的傳輸。 HDMI以主從方式工…

【Git】Commit Hash vs Change-Id

文章目錄 1、Commit 號2、Change-Id 號3、區別與聯系4、實際場景示例5、為什么需要兩者&#xff1f;6、總結附錄——Gerrit 在 Git 和代碼審查工具&#xff08;如 Gerrit&#xff09;中&#xff0c;Commit 號&#xff08;Commit Hash&#xff09; 和 Change-Id 號 是兩個不同的…

leetcode hot100刷題日記——21.不同路徑

和20題一樣的思路link 題解&#xff1a; class Solution { public:int dfs(int i,int j,vector<vector<int>>&memo){//超過了邊界&#xff0c;return 0if(i<0||j<0){return 0;}//從&#xff08;0&#xff0c;0&#xff09;到&#xff08;0&#xff0c;0…

day2 MySQL表數據操作

一&#xff1a;數據操作 注&#xff1a;在編寫MySQL代碼時可以不用區分大小寫 1.查看表結構 desc 表名; -- 查看表中的字段類型&#xff0c;長度&#xff0c;約束。 2.字段的增加 AFTER table 表名 add 字段名 數據類型; -- 默認末尾添加 after table 表名 add 字段名 …

GitAny - 無需登入的 GitHub 最新倉庫檢索工具

地址&#xff1a;https://github.com/MartinxMax/gitany GitAny - 無需登入的 GitHub 專案搜尋工具 GitAny 是一款基於 Python 的工具&#xff0c;允許你在無需登入的情況下搜尋當天最新的 GitHub 專案。它支援模糊搜尋、條件篩選以及倉庫資料的視覺化分析。 安裝依賴 $ pip…

格恩朗金屬管浮子流量計 高精度測量的不二之選?

在流量測量的復雜領域&#xff0c;精度就是生命線&#xff0c;直接關乎生產的穩定性、產品的質量以及資源的合理利用。大連格恩朗品牌的金屬管浮子流量計&#xff0c;憑借其卓越的精度表現&#xff0c;成為各行業在流量測量時的最佳之選。? 格恩朗金屬管浮子流量計運用經典的可…

【R語言編程繪圖-箱線圖】

基本箱線圖繪制 使用ggplot2繪制箱線圖的核心函數是geom_boxplot()。以下是一個基礎示例&#xff0c;展示如何用iris數據集繪制不同物種&#xff08;Species&#xff09;的萼片長度&#xff08;Sepal.Length&#xff09;分布&#xff1a; library(ggplot2) ggplot(iris, aes(…

深度學習能取代機器學習嗎?

在人工智能領域&#xff0c;“機器學習”和“深度學習”這兩個詞經常被混為一談。很多新手甚至以為只要跟 AI 有關的任務&#xff0c;都該用深度學習。但其實&#xff0c;它們并不是誰強誰弱的關系&#xff0c;而是適合不同場景的工具。 這篇文章就來幫你理清楚&#xff1a; 機…

UPS的工作原理和UPS系統中旁路的作用

UPS&#xff08;不間斷電源&#xff09;根據工作原理和適用場景的不同&#xff0c;主要分為以下三種類型&#xff0c;每種類型的特點和適用場景如下&#xff1a; 1. 后備式UPS&#xff08;Offline/Standby UPS&#xff09; 工作原理&#xff1a; 正常供電時&#xff0c;負載直接…

一級菜單401問題

正常代碼生成的前后臺文件&#xff0c;菜單類型是一級標題&#xff0c; 菜單路徑和前端組件的地址都正常寫的:/projects/xxx/xxx/xxx/XxxList 其他生成的新列表都能點進去&#xff0c;只有這個點進去就是顯示空白的像首頁那個頁面一樣&#xff0c; 問題就出現在我第一次建這…

ROS2 robot控制學習(一)

controller_position.yaml使用說明 ROS 2 的 controller_manager 用途典型工作流程示例關鍵服務與話題擴展功能JointTrajectoryController 參數詳解基本參數軌跡參數插值參數前饋控制代碼示例動態參數調試參數ForwardCommandController 概述參數解釋`joints``interface``allow_…

LightGBM的python實現及參數優化

文章目錄 1. LightGBM模型參數介紹2. 核心優勢3. python實現LightGBM3.1 基礎實現3.1.1 Scikit-learn接口示例3.1.2 Python API示例 3.2 模型調優3.2.1 GridSearchCV簡介3.2.2 LightGBM超參調優3.2.3 GridSearchCV尋優結果 在之前的文章 Boosting算法【AdaBoost、GBDT 、XGBoo…

Map集合(雙列集合)

Map結合也稱為“鍵值對集合”&#xff0c;格式&#xff1a;{key1value1&#xff0c;key2value2....} Map集合的特點&#xff1a; 鍵唯一&#xff1a;在Map集合中&#xff0c;鍵&#xff08;key&#xff09;是唯一的&#xff0c;不能有重復的鍵。如果嘗試插入一個已經存在的鍵…

springBoot項目測試時瀏覽器返回406問題解決方案

1. 如果基于最新版本的SpringBoot官方骨架創建的SpringBoot項目&#xff0c;在勾選了lombok的依賴之后&#xff0c;會在pom.xml中引入如下兩個插件&#xff1a; 2. 由于第一個插件 maven-compiler-plugin 的引入導致了這個問題&#xff0c;解決這個問題的方案呢&#xff0c;就是…

21.享元模式:思考與解讀

原文地址:享元模式&#xff1a;思考與解讀 更多內容請關注&#xff1a;深入思考與解讀設計模式 引言 在軟件開發中&#xff0c;特別是當你處理大量相似對象時&#xff0c;是否會遇到一個問題&#xff1a;大量的對象會占用大量的內存&#xff0c;而這些對象有許多相同的狀態&…

java方法重寫學習筆記

方法重寫介紹 子類和父類有兩個返回值&#xff0c;參數&#xff0c;名稱都一樣的方法&#xff0c; 子類的方法會覆蓋父類的方法。 調用 public class Overide01 {public static void main(String[] args) {Dog dog new Dog();dog.cry();} }Animal類 public class Animal {…

什么是ESLint?它有什么作用?

ESLint 是一個用于 靜態代碼分析 的工具,專門檢測 JavaScript/TypeScript 代碼中的潛在問題和風格違規。它通過預定義的規則集幫助開發者保持代碼的一致性和質量,是前端工程化的核心工具之一。 一、ESLint 的核心作用 1. 錯誤檢查(Error Detection) 識別語法錯誤、未定義變…

Docker的網絡介紹

網絡簡單介紹 在介紹 Docker 的網絡模式之前&#xff0c;先簡單說下我們在使用 Vmware 虛擬機中的網絡模式&#xff0c;形成對比&#xff0c;更好理解。 1、Vmware 中的網絡模式 1.1、VMnet0&#xff08;橋接模式&#xff09; 虛擬機通過宿主機的物理網卡直接連接到外部網絡…

Netty學習專欄(六):深度解析Netty核心參數——從參數配置到生產級優化

文章目錄 前言一、核心參數全景解析1.1 基礎網絡層參數1.2 內存管理參數1.3 水位線控制1.4 高級參數與系統級優化 二、生產級優化策略2.1 高并發場景優化2.2 低延遲場景優化 總結 前言 在分布式系統和高并發場景中&#xff0c;Netty作為高性能網絡通信框架的核心地位無可替代。…