【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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈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作为高性能网络通信框架的核心地位无可替代。…