当前位置: 首页 > news >正文

C++ 多线程实战 14|如何系统性避免死锁

目录

一、什么是死锁?

二、死锁发生的四个条件(教科书级)

三、死锁的典型场景

四、避免死锁的系统性方法

1. 保持固定的加锁顺序(最重要的规则)

2. 使用 std::lock() 一次性锁定多个互斥量

3. 使用 std::scoped_lock(C++17 推荐)

4. 尽量缩小锁的作用域

5. 避免在持锁时调用外部函数

6. 使用尝试锁(try_lock / try_lock_for)

7. 保证锁的释放(RAII 原则)

五、进阶:检测死锁的方法

六、总结:死锁的终极避坑法则

七、结语:活着比“锁死”更重要


多线程就像一群人合伙开公司,大家都很能干。
但如果沟通不畅,一个人等另一个人签字,另一个人又等前一个人批文件——
恭喜你,你的程序“卡死”了。
这就是死锁(Deadlock)。
今天,我们不讲理论公式,而是教你如何「系统性地」避免这种尴尬。


一、什么是死锁?

死锁(Deadlock)是指两个或多个线程互相等待对方释放资源,导致都无法继续执行的情况。

用生活打个比方:

  • 线程 A 拿了筷子,等碗;

  • 线程 B 拿了碗,等筷子;
    结果两人对视十秒,饭都凉了。

代码版的“吃饭问题”如下:

#include <iostream>
#include <thread>
#include <mutex>std::mutex chopstick;
std::mutex bowl;void threadA() {std::lock_guard<std::mutex> lock1(chopstick);std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟操作std::lock_guard<std::mutex> lock2(bowl);std::cout << "A 吃上饭了!\n";
}void threadB() {std::lock_guard<std::mutex> lock1(bowl);std::this_thread::sleep_for(std::chrono::milliseconds(100));std::lock_guard<std::mutex> lock2(chopstick);std::cout << "B 吃上饭了!\n";
}int main() {std::thread t1(threadA);std::thread t2(threadB);t1.join();t2.join();
}

运行后——程序卡死了。
两个线程都拿着对方需要的锁,一直等到地老天荒。


二、死锁发生的四个条件(教科书级)

死锁的产生,必须满足以下四个条件:

条件说明
互斥资源一次只能被一个线程使用
请求与保持线程持有资源的同时又请求新的资源
不剥夺已获得的资源不能被强制剥夺
循环等待多个线程形成一个循环等待链

破解死锁的关键
打破其中任意一个条件,死锁就无法成立!


三、死锁的典型场景

  1. 双重锁定顺序不一致
    两个线程获取锁的顺序不同,是最常见的死锁原因。

  2. 递归锁滥用
    自己锁自己多次而不解锁。

  3. 错误的条件等待
    锁没释放却调用了 wait()sleep()

  4. 多资源嵌套锁定
    一次性锁多个资源时没处理好顺序。


四、避免死锁的系统性方法

我们来从实践角度出发,讲如何「系统性」地避免死锁,而不是靠“经验”瞎猜。


1. 保持固定的加锁顺序(最重要的规则)

给所有资源定义「加锁顺序」,所有线程严格遵守相同顺序加锁

举个例子:

  • 对象 A 的锁 ID = 1

  • 对象 B 的锁 ID = 2
    那么所有线程都必须「先锁 ID 小的,再锁 ID 大的」。

示例:

#include <iostream>
#include <mutex>
#include <thread>std::mutex m1, m2;void safe_thread(int id) {if (id == 1) {std::lock_guard<std::mutex> lock1(m1);std::lock_guard<std::mutex> lock2(m2);std::cout << "线程 1 正常运行\n";} else {std::lock_guard<std::mutex> lock1(m1);  // 注意顺序一致std::lock_guard<std::mutex> lock2(m2);std::cout << "线程 2 正常运行\n";}
}int main() {std::thread t1(safe_thread, 1);std::thread t2(safe_thread, 2);t1.join();t2.join();
}

结果:
✅ 没有死锁。
因为所有线程都按相同顺序获取锁。


2. 使用 std::lock() 一次性锁定多个互斥量

std::lock() 是 C++ 提供的“死锁安全锁定”工具。
它内部保证了不会因为锁顺序不同而死锁

std::mutex m1, m2;void worker() {std::lock(m1, m2);  // 一次性锁两个std::lock_guard<std::mutex> lock1(m1, std::adopt_lock);std::lock_guard<std::mutex> lock2(m2, std::adopt_lock);std::cout << "安全地锁定多个资源\n";
}

std::adopt_lock 的意思是告诉 lock_guard
“这些锁我已经拿到了,不需要再去 lock() 一次。”

这种写法优雅又安全,非常推荐。


3. 使用 std::scoped_lock(C++17 推荐)

从 C++17 开始,标准库引入了 std::scoped_lock,这是一个“自动同时锁多个 mutex”的神器。

std::mutex m1, m2;void safe_func() {std::scoped_lock lock(m1, m2); // 同时加锁,无死锁风险std::cout << "安全执行中...\n";
}

std::scoped_lockstd::lock() + adopt_lock 的语法糖,写法简洁,不容易出错。


4. 尽量缩小锁的作用域

锁用得越多,风险越高。
锁住的代码块越小越好。

坏例子:

std::lock_guard<std::mutex> lock(m);
do_something_slow();  // 锁期间做大量计算

好例子:

{std::lock_guard<std::mutex> lock(m);quick_update();    // 锁期间只做必要操作
}
do_something_slow();   // 锁外执行

5. 避免在持锁时调用外部函数

外部函数里可能也会加锁或等待,引发隐性死锁。
所以尽量不要在持锁状态下调用未知代码


6. 使用尝试锁(try_lock / try_lock_for

如果获取不到锁,立刻返回或等待一段时间。
不会死等,也就不会死锁。

if (m.try_lock_for(std::chrono::milliseconds(100))) {// 获取成功m.unlock();
} else {std::cout << "超时放弃,避免死锁。\n";
}

7. 保证锁的释放(RAII 原则)

std::lock_guardstd::unique_lock 包装锁,自动释放,防止忘记 unlock()

不要再写这种代码了:

m.lock();
do_stuff();
m.unlock();

一旦 do_stuff() 抛异常,程序会崩。

lock_guard

{std::lock_guard<std::mutex> guard(m);do_stuff();
} // 自动 unlock

五、进阶:检测死锁的方法

  1. 调试器卡住时,检查线程堆栈
    看是否有多个线程在等待 lock()

  2. 使用工具

    • Clang ThreadSanitizer (-fsanitize=thread)

    • Visual Studio Concurrency Visualizer

    • Valgrind 的 helgrind

这些工具能检测潜在的锁冲突与死锁。


六、总结:死锁的终极避坑法则

方法说明
固定加锁顺序保证所有线程顺序一致
使用 std::lockscoped_lock一次性安全加锁
减少锁粒度缩小锁定区域
避免嵌套加锁一层锁一件事
避免持锁调用外部函数外部代码不可控
使用尝试锁超时放弃
RAII 管理锁自动释放,不怕异常

七、结语:活着比“锁死”更重要

多线程的世界里,死锁像个狡猾的陷阱。
它不报错、不闪退,只是——静静地卡住,像时间凝固。

而真正的高手,不是写出复杂的多线程代码,
而是写出再复杂也不会死锁的代码

别让线程互相等待到天荒地老,
毕竟,程序要跑,生活还得继续。

http://www.dtcms.com/a/496381.html

相关文章:

  • 平顶山股票配资网站建设网站首页轮播图怎么做
  • C4D域-顶点贴图及布料的综合运用:打造精准布料动画控制
  • 昆明专业网站设计公司iis v6 新建网站
  • 北理工网站开发与运用营销型网站建设深圳
  • MVVMLight
  • 网站在服务器wordpress执行生命周期
  • 南昌做网站哪家公司比较好怎么设置wordpress底栏文字
  • 成都住房和城乡建设局 网站甘肃省建设银行网站
  • 《自动控制原理》第 2 章 线性控制系统的数学描述:2.3、2.4
  • C++ try-catch 异常处理机制详解
  • 各大网站搜索引擎wordpress 公告
  • 网站会员体系方案全国最新网站备案查询
  • 快手one系列核心合集随笔 (随着系列推出更新)
  • 自己做的网页怎么上传到网站吗wordpress 图片 主题
  • 【RocketMQ】RocketMQ原生 API 实现的分布式事务完整方案
  • 江苏省住房和城乡建设局网站首页网站图片文字排版错误
  • 做网站自动赚钱十堰seo优化教程
  • AUTOSAR图解==>AUTOSAR_AP_TR_SystemTests
  • 手机网站转微信小程序网上商城运营推广思路
  • 乡村振兴 统筹发展PPT(63页)
  • 沈阳网站选禾钻科技有哪些网页设计软件
  • instanceof和类型转换
  • MySQL 企业版数据脱敏与去标识化
  • 物流信息网站wordpress下载样式
  • 网站建设与维护要用到代码吗网站实用性
  • 常州住房和城乡建设部网站北京建设集团网站
  • 正规的GEO优化师培训哪家好
  • 建设银行甘肃省行网站wordpress请求接口的方式
  • 怎么开网站做网红淮安网站建设公司
  • 昌平建设网站徐州seo推广优化