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

死锁 (Deadlock) 深度解析 - 详解

目录

1. 什么是死锁?一个生活中的比喻

2. 死锁的四个必要条件(科夫曼条件)

a. 互斥 (Mutual Exclusion)

b. 持有并等待 (Hold and Wait)

c. 不可抢占 (No Preemption)

d. 循环等待 (Circular Wait)

3. 如何处理死锁?

死锁预防:破坏四个必要条件之一

4. C++ 标准库提供的“死锁安全带”

std::lock 和 std::scoped_lock (C++17)


1. 什么是死锁?一个生活中的比喻

想象一个场景:在一个狭窄的单行道上,两辆车迎面相遇。

  • 车A 想要前进,但被 车B 挡住了。

  • 车B 也想前进,但被 车A 挡住了。

两辆车都占着自己当前的路(持有资源),同时又在等待对方让出道路(请求资源)。如果两位司机都互不相让,他们就会永远卡在这里,谁也动不了。这种情况,就是死锁

在并发编程中,“车”就是线程,“道路”就是资源(最常见的是互斥锁 std::mutex)。

死锁的正式定义:指两个或多个并发线程,在执行过程中因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。

2. 死锁的四个必要条件(科夫曼条件)

一个死锁的发生,必须同时满足以下全部四个条件。只要破坏其中任意一个,死锁就不会发生。

a. 互斥 (Mutual Exclusion)
  • 定义:一个资源在同一时刻只能被一个线程持有。如果其他线程想使用该资源,必须等待直到资源被释放。

  • 比喻:打印机在任何时候只能打印一个人的文件。在你打印完成之前,其他人必须等待。

  • 编程体现std::mutex 本身就是互斥的。lock() 操作确保了只有一个线程能进入临界区。这个条件在并发编程中通常是无法避免的,因为我们就是需要用锁来保护共享资源。

b. 持有并等待 (Hold and Wait)
  • 定义:一个线程至少持有一个资源,并且正在请求另一个被其他线程持有的资源。在等待新资源的同时,它并不会释放自己已经持有的资源。

  • 比喻:你手里拿着一支笔(持有资源1),现在你需要一张纸来写字,但纸在另一个人手里。你在等待他给你纸的同时,并没有放下你手中的笔。

  • 编程体现

    std::lock_guard lock1(mtx1); // 持有 mtx1
    // ...做一些事...
    std::lock_guard lock2(mtx2); // 等待 mtx2
c. 不可抢占 (No Preemption)
  • 定义:资源不能被强制地从持有它的线程中“抢”走。只能由持有者在完成任务后自愿释放。

  • 比喻:别人不能从你手里强行夺走你正在使用的笔。你必须自己决定什么时候用完并把它放下。

  • 编程体现:当一个线程通过 lock() 获得了互斥锁,操作系统或其他线程无法强制它 unlock()。它必须自己执行到作用域结束(对于 lock_guard)或手动调用 unlock()

d. 循环等待 (Circular Wait)
  • 定义:存在一个线程的等待链,形成一个闭环。

    • 线程 T1 正在等待线程 T2 持有的资源。

    • 线程 T2 正在等待线程 T3 持有的资源。

    • ...

    • 线程 Tn 最终等待线程 T1 持有的资源。

  • 比喻

    • 想借小明的钢笔,但小明说必须先借到小红的笔记本才行。

    • 小红说,她必须先借到的橡皮擦,才肯借出笔记本。

    • 于是,你等小明,小明等小红,小红又在等你。三个人陷入了无限的循环等待。

  • 最经典的编程场景

    // 线程 1
    void func1() {std::lock_guard lock1(mtx1); // 1. 锁住 mtx1std::this_thread::sleep_for(std::chrono::milliseconds(10));std::lock_guard lock2(mtx2); // 2. 尝试锁住 mtx2 (等待线程2释放)
    }
    // 线程 2
    void func2() {std::lock_guard lock2(mtx2); // 1. 锁住 mtx2std::this_thread::sleep_for(std::chrono::milliseconds(10));std::lock_guard lock1(mtx1); // 2. 尝试锁住 mtx1 (等待线程1释放)
    }

    如果 func1func2 并发执行,func1 可能锁住 mtx1 等待 mtx2,而 func2 同时锁住了 mtx2 等待 mtx1,这就形成了循环等待,导致死锁。

3. 如何处理死锁?

主要有三种策略:预防、避免、检测与恢复。在应用级编程中,我们最关心的是死锁预防

死锁预防:破坏四个必要条件之一

预防死锁的核心思想,就是通过编码规范和设计,来破坏四个必要条件中的至少一个。

  • 破坏“互斥”

    • 方法:允许多个线程同时访问资源。

    • 可行性:很低。对于打印机、共享计数器这类资源,我们就是为了互斥才加锁的。但对于只读数据,可以使用无锁数据结构或原子操作 std::atomic 来代替互斥锁。

  • 破坏“持有并等待”

    • 方法:规定线程在请求资源前,不能持有任何其他资源。要么一次性申请所有需要的资源,要么在申请新资源前,先释放已持有的所有资源。

    • 可行性:较低。很难预知一个任务到底需要哪些资源,且“先释放再申请”可能导致线程活锁(不断尝试但总失败)。

  • 破坏“不可抢占”

    • 方法:允许系统或优先级更高的线程抢占资源。

    • 可行性:极低。在应用层面几乎无法实现,且会使程序逻辑变得异常复杂。

  • 破坏“循环等待” (最实用、最常用的策略)

    • 方法对所有资源(互斥锁)进行全局排序,并强制所有线程都必须按照这个统一的顺序来获取锁。

    • 可行性:非常高,是预防死锁的黄金法则

    • 比喻:规定所有司机在十字路口都必须先让右边的车。这样就不会出现所有车都想抢先而堵死的局面。

    • 编程实现

      std::mutex mtx1, mtx2;
      // 假设我们规定,必须先锁地址较小的互斥量
      // 这是一个全局统一的顺序
      // 线程 1
      void func1_fixed() {// 总是按 mtx1 -> mtx2 的顺序加锁std::lock_guard lock1(mtx1);std::lock_guard lock2(mtx2);
      }
      // 线程 2
      void func2_fixed() {// 同样遵守 mtx1 -> mtx2 的顺序std::lock_guard lock1(mtx1);std::lock_guard lock2(mtx2);
      }

      通过强制所有线程都遵循 mtx1 -> mtx2 的加锁顺序,就打破了循环等待的可能性,从根本上消除了死锁。

4. C++ 标准库提供的“死锁安全带”

手动管理锁的顺序很麻烦且容易出错。为此,C++ 标准库提供了更高级的工具来自动处理这个问题。

std::lockstd::scoped_lock (C++17)

当你需要同时锁定多个互斥锁时,应该使用它们。

  • std::lock(mtx1, mtx2, ...): 这是一个函数,可以一次性、无死锁地锁定传入的所有互斥锁。它内部实现了一套避免死锁的算法(例如,它会尝试锁定,如果某个锁失败了,它会解开所有已锁定的锁,然后重试)。

  • std::scoped_lock(mtx1, mtx2, ...) (C++17, 最佳选择): 这是一个 RAII 风格的类,是 std::lock 的现代化封装。它在构造时自动调用 std::lock 来安全地锁定所有互斥锁,在析构时(离开作用域时)自动将它们全部解锁。

#include 
#include 
std::mutex mtx_A, mtx_B;
void safe_operation() {// 只需要一行代码,就能安全、无死锁地锁定两个互斥锁// 无需关心 mtx_A 和 mtx_B 的顺序std::scoped_lock lock(mtx_A, mtx_B);// ... 在此作用域内,mtx_A 和 mtx_B 都被安全锁定 ...// ... 执行你的操作 ...
} // lock 析构时,会自动解锁 mtx_A 和 mtx_B

结论:在需要同时锁定多个互斥锁的场景下,永远优先使用 std::scoped_lock。这是现代 C++ 中预防死锁的最佳实践。

http://www.hskmm.com/?act=detail&tid=38919

相关文章:

  • 解压小猫
  • 2025年家具厂家推荐排行榜:实木家具、定制家具、办公家具、软体家具、智能家具源头厂家精选
  • 2025 厨房排烟安装公司最新推荐榜:实力品牌服务与工艺解析及选择指南饭店/商用/罩/系统/厨房排烟管道专业服务公司推荐
  • 权威调研榜单:苏州吊车租赁公司TOP3榜单好评深度解析
  • 从零开始制作 MyOS(二)
  • 2025年高压加速老化设备厂家推荐排行榜,高压加速老化HAST,高压加速老化PCT,热流仪源头厂家专业测评与选购指南
  • 2025年摄像头防爆外壳供货商权威推荐榜单:监控摄像头外壳/摄像头外壳/路口警示道灯外壳源头厂家精选
  • 2025 液态硅胶设备源头厂家最新推荐榜:行业协会权威测评发布,品质与服务双维度精选
  • 2025 年阳台光伏厂家最新推荐排行榜:权威测评解析逆变器、储能及光伏板优质企业阳台太阳能光伏/储能/发电/阳台光伏板优质厂家推荐
  • 2025年废气治理设备厂家推荐排行榜,废气处理设备,工业废气净化装置,有机废气处理系统公司精选
  • 2025 年钛白粉源头厂家最新推荐排行榜:高分子材料领域专家解析改性技术与行业应用案例
  • 2025 年泳池设备厂家推荐:Firsle 法思乐泳池水处理与海洋馆维生系统设备专业方案及一体化设备优势解析
  • 2025年托辊输送带直销厂家权威推荐榜单:输送机托辊/托辊设备/托辊配件源头厂家精选
  • 2025年提升机厂家权威推荐榜:自动提升机、垂直提升机、斗式提升机,高效输送设备源头厂家精选
  • 2025 年最新冲压油供应厂家权威榜单:聚焦空调加工适配性与免清洗技术,助力企业精准选品免清洗/铝翅片/定子转子/高速冲压油厂家推荐
  • 2025年企业数字化展厅定制厂家权威推荐榜单:企业数字展厅/企业创意展厅/企业智能展厅源头厂家精选
  • 2025年仿石漆厂家推荐排行榜,外墙仿石漆,真石漆,质感涂料,水包砂,仿石涂料优质供应商精选
  • 实用指南:Nginx 访问控制、用户认证、HTTPS配置实操手册
  • 2025年清洗剂厂家权威推荐榜:水基型清洗剂、工业清洗剂、精密仪器清洗剂源头厂家综合测评与选购指南
  • 蓝队中的SOC角色解析:从初级分析师到职业发展路径
  • Electron 应用自动更新方案:electron-updater 完整指南
  • 2025年包装机厂家权威推荐榜单:自动包装机、半自动包装机最新选购指南与行业趋势解析
  • 10/24
  • 《ESP32-S3使用指南—IDF版 V1.6》第四十三章视频播放器实验
  • docker安装运行nginx
  • 2025年摩托车/机车厂家推荐排行榜:街车机车,巡航机车,越野机车,跑车机车,复古机车品牌厂家精选推荐
  • 2025 年载冷剂厂家最新推荐排行榜:权威协会测评 + 京津冀区位优势,严检达标优选指南超低温/乙二醇/冷库专用/食品级载冷剂公司推荐
  • Unreal:多屏幕如何设置曝光同步
  • 2025 年国内防冻液厂家最新推荐排行榜:严检合格、技术为先的实力企业权威甄选乙二醇防冻液/食品级鲜冻液/空气能专用防冻液/长效防冻液公司推荐
  • 2025年环保设备厂家推荐排行榜,废气处理设备,废水处理设备,噪音治理设备公司推荐,专业实力与环保方案深度解析