C++每日训练 Day 19:死锁原理与实战剖析(打印机 + 扫描仪模型)

3dMax脚本类插件:不写 C++也能造“原生”工具 从plugin关键字到场景持久化,拆解 MAXScript 插件开发全流程! 阅读详情

支持作者新书,点击京东购买《Yocto项目实战教程:高效定制嵌入式Linux系统》

📘 本篇聚焦 死锁(Deadlock) 的本质机制、触发条件、经典场景和代码演示。通过模拟现实办公设备中的“打印机 + 扫描仪”资源争夺,让你彻底理解两个线程如何互相等待导致死锁,并掌握如何分析、预防与破除。


🔁 Day 18 回顾

在 Day 18 中,我们构建了一个响应式表单系统,协程异步验证用户输入并反馈结果。

主要内容包括:

  • 协程机制结合 UI 表单事件
  • 使用 co_await 实现自然表达的等待验证
  • 多字段输入与验证反馈

这些为我们理解多线程控制结构与资源管理打下了基础。


🎯 今日目标:理解并解决 C++ 多线程中的死锁问题

学习模块核心内容
死锁定义与四要素如何判断、何时发生
现实模拟:打印机+扫描仪具象化死锁情景,提升理解
死锁代码演示两个线程、两个资源、互等导致死锁
死锁预防与解决策略四种方案:资源排序、尝试锁定、RAII、调度拆分

🧩 一、什么是死锁?

死锁是指两个或多个线程因互相等待对方持有的资源,而导致永远无法继续执行的现象。

✅ 四个必要条件(环环相扣):

  1. 互斥:资源一次只能被一个线程占用
  2. 占有并等待:线程已占有资源并继续请求其他资源
  3. 不可抢占:资源只能被主动释放
  4. 循环等待:线程之间形成环状资源等待链

📌 只要同时满足这四个条件,系统就可能进入死锁状态。


在这里插入图片描述

🔒 二、经典例子:打印机 + 扫描仪模型(具象化)

💡 场景设定:

  • A 想先用 打印机,再用 扫描仪
  • B 想先用 扫描仪,再用 打印机

如果 A 成功锁住了打印机,B 成功锁住了扫描仪,然后两人都想获取对方正在占用的资源,就会发生死锁。


💻 三、代码演示:两线程死锁实现

#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>

std::mutex printer;
std::mutex scanner;

void personA() {
    std::cout << "A 想用打印机\n";
    printer.lock();
    std::cout << "A 拿到打印机\n";

    std::this_thread::sleep_for(std::chrono::milliseconds(100));

    std::cout << "A 想用扫描仪\n";
    scanner.lock();
    std::cout << "A 拿到扫描仪\n";

    scanner.unlock();
    printer.unlock();
    std::cout << "A 完成任务\n";
}

void personB() {
    std::cout << "B 想用扫描仪\n";
    scanner.lock();
    std::cout << "B 拿到扫描仪\n";

    std::this_thread::sleep_for(std::chrono::milliseconds(100));

    std::cout << "B 想用打印机\n";
    printer.lock();
    std::cout << "B 拿到打印机\n";

    printer.unlock();
    scanner.unlock();
    std::cout << "B 完成任务\n";
}

int main() {
    std::thread t1(personA);
    std::thread t2(personB);
    t1.join();
    t2.join();
    return 0;
}

🔍 输出(典型死锁):

A 想用打印机
A 拿到打印机
B 想用扫描仪
B 拿到扫描仪
A 想用扫描仪
B 想用打印机
(卡住)

❗ 四、分析死锁触发的根源

线程第一步第二步
A锁住打印机等待扫描仪
B锁住扫描仪等待打印机

两个线程各持一个锁,又都想获得另一个锁 → 满足死锁的四个条件。


✅ 五、解决方案与最佳实践

✅ 方案一:统一加锁顺序(推荐)

void safePersonA() {
    std::lock(printer, scanner);
    std::lock_guard<std::mutex> lock1(printer, std::adopt_lock);
    std::lock_guard<std::mutex> lock2(scanner, std::adopt_lock);
    std::cout << "A 成功拿到所有资源\n";
}

📌 原理:所有线程按照相同顺序申请资源,不会形成“环状等待链”

✅ 方案二:使用 std::scoped_lock(C++17)

void safePersonA() {
    std::scoped_lock lock(printer, scanner);
    std::cout << "A 成功拿到所有资源\n";
}

✅ 方案三:尝试加锁 + 超时退出(try_lock)

if (printer.try_lock()) {
    if (scanner.try_lock()) {
        // 成功
        scanner.unlock();
    }
    printer.unlock();
}

✅ 方案四:RAII 资源控制(封装类自动释放)

  • 始终用 lock_guard 或 scoped_lock
  • 防止因为异常或 return 提前退出导致未释放

🧠 巩固练习题

Q1:什么是死锁?死锁产生的四个条件是什么?

A:互斥、占有并等待、不可抢占、循环等待

Q2:写出一个实际生活中的死锁例子?

A:两个银行账户互相转账时,各自锁住了自己的账户信息,等待对方 → 死锁。

Q3:如何避免死锁?

A:统一资源获取顺序、使用 scoped_lock、使用 try_lock、合理释放锁。


📘 购书推荐支持作者

📗 《Yocto 项目实战教程:高效定制嵌入式 Linux 系统》
👉 点击购买(京东自营)


📙 总结回顾

  • 死锁本质是 多个线程之间形成“资源相互等待链”
  • 必须理解四要素,才能准确分析问题
  • 避免死锁 = 统一锁顺序、使用并发安全工具、释放要及时
  • 通过打印机 + 扫描仪场景,深入理解“争夺 + 等待 + 不释放”的死锁过程

📌 期待你在实际项目中识别并解决死锁,让多线程系统更稳定可靠!

支持作者新书,点击京东购买《Yocto项目实战教程:高效定制嵌入式Linux系统》

医学图像标注工具教程——从零开始使用3D Slicer进行医学图像手动分割 3D Slicer手动分割软件下载及安装数据导入导入CT序列文件夹导入单张超声基本交互操作窗位和窗宽的调整改变切片及寻找特定点对应的三维切片图像分割功能介绍PaintDrawEraseLevel tracingGrow from seedsFill between slicesThreshold & Margin & HollowSmoothingScissors & IslandsLogical operator分割结果的保存 软件下载及安装 下载地址:官网的4.11.0版本 数据 阅读详情

相关推荐

Qt UI界面美化教程1:【“飞扬青云” Qt精美控件】使用教程1

相信很多小伙伴在使用Qt Widget的过程中都会遇到一个问题,开发出来的界面,丑!丑!丑!当然Qt做界面美化可以用到QSS,需要对前端知识有一定了解,后续本套教程将会做讲解。本篇文章,我们直接站在巨人(大佬飞扬青云)的肩膀上,使用他开发好的163款自定义控件,请大家一定支持他,去给他小心心,他的个人主页:本篇文章只讲述如何在 QtDesigner 安装这些控件。

2401_87556704的博客 1419

选型笔记:Type‑C 扩展坞方案评估|DP1.4 转 HDMI2.0 桥接芯片 GSV2201S@ACP

本文记录 GSV2201S 这款 DP1.4a 转 HDMI2.0 桥接芯片评估,梳理视频、HDCP、PD 集成能力,总结 Type‑C 转接 Dongle、扩展坞方案的适配边界,以及差分走线、PD 时序、HDCP 握手等高频调试问题,为 C 口视频转接方案选型提供参考。

ACP广源盛的博客 213

SRC漏洞挖掘实战指南:从零入门到月入过万的网络安全副业

网络安全的核心在于识别和修复系统缺陷,其中逻辑漏洞与信息泄露是常见的安全风险。其原理在于应用程序在业务逻辑设计或数据处理过程中存在缺陷,导致未授权访问或敏感数据暴露。掌握这些技术对于构建安全的应用体系至关重要,不仅能有效防范攻击,也为安全研究人员提供了将技术能力变现的合法途径。在漏洞赏金(Bug Bounty)和SRC(安全应急响应中心)等应用场景中,白帽子通过系统化的测试方法,挖掘此类漏洞,既能获得经济回报,又能积累宝贵的实战经验。本文聚焦于逻辑漏洞与信息泄露的实战挖掘技巧,为初学者提供清晰的入门路径和效

weixin_28568819的博客 346

C++移动构造函数详解

移动构造函数是C++11引入的一种特殊构造函数,用于从临时对象(右值)"窃取"资源来初始化新对象,而不是进行昂贵的深拷贝。char* data;public:// 移动构造函数:参数是右值引用 &&// 关键:把源对象的资源置空。

拒绝AI文,努力在AI时代写出感动人心的好文章 385

5.1 Tessent Atpg系列 第八章 Test Pattern Generation - Verify Test Pattern

为什么要做基于timing的仿真,意义在于可以比较ATPG工具和simulator工具的差异性。因为时序的差异导致不同的结果。首先你需要通过write_patern 这句cmd来写出pattern,默认是parallel 模式,除非你用-serial 指定写出serial, 因为serial 文件非常大,所以你可以使用-sample 开关写出部分pattern即可,工具会根据每一种pattern 类型都会至少保存一只。 另外,你也可以使用-start -end 开关来减少文件大小,但是这样的截取,是不可靠的

ciscomonkey的博客 7759

[Ethernet in CANoe]4--CANoe工程中SOME/IP报文的详细介绍(二)

介绍了SOME/IP报文的SD报文(ServiceDiscovery),本片文章开始继续介绍SOME/IP报文。

汽车ECU自动化测试 1111

ThunderKittens 深度解析:一个不基于 MLIR 的 C++ 模板 GPU DSL

Triton 是"让更多人能写 GPU kernel",ThunderKittens 是"让会写的人写得更爽"。前者用 MLIR 换易用性,后者用 C++ 模板换可控性。它们不是替代关系,而是覆盖了 GPU 编程光谱的两端。ThunderKittens 的出现,本质上是对"GPU DSL 必须基于 MLIR"这一默认假设的挑战。编译期类型系统本身就可以是一个强大的 DSL 基础设施,不需要中间 IR,不需要黑盒 pass,也能实现 tile 级抽象和零运行时开销。

Maxwell_Newton的博客 310

行政数据分析大屏实战:用Tableau从Excel到看板的完整搭建流程(含数据连接与字段计算避坑)

本文详细介绍了如何利用Tableau从Excel数据源搭建行政数据分析大屏的完整流程,涵盖数据准备、连接技巧、计算字段优化、看板设计及动态交互部署。通过五阶工作流,帮助用户高效处理多源异构数据,实现业务洞察与可视化呈现,提升行政数据分析效率。

weixin_29696711的博客 336

[C++]:6.STL:vector

今天就要开始学vector容器,这节课我们主要学习vector中经常会用到的函数,并且给出该函数的模拟实现。话不多说,直接上干货。下面是本章的思维导图,那么,让我们开始吧!

2301_80817679的博客 136

AD9361 Fast Lock实战:Python脚本自动生成Profile寄存器配置(附完整代码)

本文详细介绍了利用Python脚本实现AD9361 Fast Lock Profile寄存器自动化配置的实战方案。通过解析频率合成公式、VCO约束及查表机制,脚本能根据目标频率一键生成完整的16进制寄存器序列,大幅提升射频系统开发中多频点切换的效率和准确性,并支持CSV、JSON、C头文件等多种输出格式,便于与FPGA/嵌入式系统集成。

weixin_29194693的博客 474

BACnet协议栈基本对象BI、BO、AI、AO、AV以及device和File对象介绍

BACnet协议栈基本对象BI、BO、AI、AO、AV以及device和File对象介绍

shufawangzhang的博客 3984

蓝桥杯·全国青少年信息素养大赛·电子学会Python/C++考级:倍增法求最近公共祖先LCA解析

蓝桥杯报名正在火热进行(软件赛报名 2026-10-08—12-18,省赛 2027-04-10),电子学会 12 月软件编程(C/C++ 一级至十级、Python 一级至六级)考级报名也已开启(9/23—12/2),全国青少年信息素养大赛算法应用主题赛同步推进——把 LCA 这个模板练熟,正是冲刺这些赛事树型考点的关键一步。所谓「最近公共祖先」:在一棵以某个节点为根的树里,两个节点 u、v 向上追溯,最早交汇的那个祖先就叫它们的 LCA。比如家谱里,你和你表哥最近的共同长辈就是你们的祖父。

IT_Scratch的博客 530

GDB交叉编译与问题解决:从零到精通的深度指南

完成后,GDB会安装到/usr/local/gdb-aarch64/bin,可以用aarch64-linux-gnu-gdb --version检查。:得有支持目标架构的交叉编译工具链,比如gcc-arm-none-eabi(用于ARM裸机)或aarch64-linux-gnu-gcc(用于ARM Linux)。:在配置工具链时,运行aarch64-linux-gnu-gcc --version检查版本,确保工具链没问题。这种场景下用GDB调试,就像在荒野求生,但别慌,GDB依然是你的好伙伴。

大模型大数据攻城狮的专栏 1万+

C++ 多态详解:作用、原理与实战用法

多态是C++面向对象的核心特性,通过虚函数实现运行期动态绑定,使同一接口在不同对象上表现出不同行为。其核心作用包括统一接口、提升可扩展性、降低耦合、支持运行时分派与批量处理。实现依赖虚函数表(vtable)和虚指针(vptr),需注意虚析构函数、对象切片等陷阱。相比编译期多态(模板),运行期多态灵活性高但有调用开销。合理结合两者,可兼顾代码的灵活性与性能,是构建可扩展、低耦合系统的关键。

2402_85743155的博客 367

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

【C语言】字符与字符串函数

C语言中字符串为以\0结尾的字符数组,标准库函数基于此约定。本文解析字符串内存布局,详解ctype.h与string.h中字符分类、长度计算、拷贝、比较等函数的使用要点,强调长度判断与越界风险,最后通过命令行待办事项示例整合fgets、sscanf、strncpy、strcmp,展示实际应用中的关键细节。

记录在学习过程中学到的知识 334

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

C++ 函数模版

模板参数:● template ,T 就是类型占位符,名字可以自定义,习惯用T● 占位符可以在函数返回值、形参、函数内部当作类型使用使用要点模板代码一般写在头文件:编译器需要看到完整模板源码,才能在每个编译单元完成实例化全局函数、类内成员函数都可以写成模板实例化发生在编译阶段,不是运行时隐式实例化:调用时编译器自动推导实参类型,生成对应版本函数。

2501_94041724的博客 184

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

C++ 运行时多态与虚函数机制全面剖析【面试重点】

构成C++ 运行时多态必须是基类的指针或者引用调用虚函数。被调用的函数必须是虚函数,并且派生类必须对该虚函数完成重写/覆盖(Override)。只有基类的指针或引用才能既指向基类对象,又指向派生类对象(切片机制);而只有完成了虚函数重写,派生类与基类才具备不同的函数行为,达到“指向谁就调用谁”的效果。在类成员函数的前面加上virtual关键字修饰,该成员函数就成为了虚函数。public:// 成员函数前加 virtual 修饰,声明为虚函数。

Fuyo_0209的博客 433

C++ 求抛物线的顶点、焦点和准线(Finding the vertex, focus and directrix of a parabola)

已知抛物线方程 $ y = ax^2 + bx + c $,可利用公式求其顶点、焦点和准线。顶点为 $ \left( -\frac{b}{2a}, \frac{4ac - b^2}{4a} \right) $,焦点为 $ \left( -\frac{b}{2a}, \frac{4ac - b^2 + 1}{4a} \right) $,准线方程为 $ y = c - \frac{b^2 + 1}{4a} $。以 $ a=5, b=3, c=2 $ 为例,计算得顶点 $(-0.3, 1.55)$,焦点 $(-0

csdn_aspnet的专栏,请点击博客主页右上角三个点中的私信联系 163

c++ 超级大坑未定义行为如何避免(下)

尤其是通过。

拒绝AI文,努力在AI时代写出感动人心的好文章 141

华为校招笔试真题 10.28 - 稀疏整数求和(C++/Py/Java /Js/Go)

华为非AI笔试 10月28号真题,华为笔试第二题【稀疏整数求和】,提供详细思路讲解以及Java、C++、Python、Go、JavaScript多语言代码实现。华为非AI笔试笔试真题【稀疏整数求和】多语言题解。

qq_45776114的博客 612

C 语言指针与输入输出

指针是 C 语言的魂,输入输出是和人交互的入口。这篇文章把"变量在内存里到底长什么样"讲透,接着用代码验证原码/反码/补码为什么非用补码不可,再拿进制转换、数组、printf/scanf 的格式符收尾。所有输出都是本机 gcc 真实跑出来的。

2401_89953644的博客 151

从“不照人”到“剂量达标”:扇叶遮挡式UV-C空气消毒的工程边界与可验证评估框架

人机共存”正逐渐成为家用 UV-C 空气消毒产品的核心卖点。其中一类常见结构,是把 254 nm 紫外线灯置于吊扇扇叶上方,以扇叶盘作为物理遮挡,同时借助风扇强制对流实现空气循环消毒。专利文献对该结构的意图描述相当清晰:消毒平台遮挡紫外线灯朝下发出的光,使光线射向天花板,从而“不会让紫外线辐射到位于吊扇灯下方的用户”。然而,“结构上不直射”与“工程上安全且有效”之间,仍隔着一段需要实测数据来填补的距离。

HANDY09200的博客 684

【C++】【STL】string_OJ题

本文总结了9道C++ STL中关于string的OJ题目,按操作类型分为三组:反转与就地修改(如917、541、151)、计数与查找(如387、125、58)、模拟与大数运算(如415、43、8)。核心技巧包括双指针收敛、字符分类处理、进位控制及边界值处理。所有题目使用同一套源码,在标准库std::string与自定义容器MyString间切换仅需宏定义,验证结果一致性。代码通过随机测试用例与Trace函数逐行比对,确保逻辑正确性,强调接口对齐的重要性。

这里是Cocatrice的博客,感谢大家一直以来的支持和鼓励,如果对C/C++方向感兴趣的话,可以通过主页的微信名片加Cocatrice的好友,也可以通过邮箱:Chen31980094@163.com 联系,一同加油! 4355

C 语言 malloc() 详解与实战 Demo

本文详解C语言malloc()函数,涵盖其语法、内存原理及实战应用。通过动态整数、数组、结构体与链表的Demo,演示如何安全申请、使用与释放内存,强调避免内存泄漏与空指针检查的重要性,帮助开发者掌握动态内存管理核心技能。

tangPHP的博客 361

【C++ 基础入门】类和对象(上):封装、对象模型与 this 指针

C++类与对象知识(面向过程和面向对象的初步认识,类的初步认识,类的访问限定符及封装,类的作用域、实例化,类的对象模型,this指针)

2601_96587588的博客 862

C++09_vector类复习

本文以10个核心问题为主线,系统解析C++ vector的底层机制:从基本概念、初始化方式、遍历方法,到size与capacity的区别、扩容策略(VS 1.5×,g++ 2×)、reserve与resize差异、中间增删的挪动原理、迭代器失效原因,直至二维vector的内存布局(含三指针结构)。通过“先会用、再懂扩容、最后看清对象结构”的递进逻辑,揭示vector作为动态数组的本质——连续内存、自动扩容、随机访问高效,但插入删除代价高,迭代器易失效。全文深入浅出,助读者摆脱“黑盒”认知,真正掌握其内存图景

lemo_n_的博客 336
上一篇: 设计模式每日硬核训练 Day 18:备忘录模式(Memento Pattern)完整讲解与实战应用
下一篇: Linux ftrace基础入门教程:内核调试者的强大利器
嵌入式孙老师
博客等级 码龄7年 5686粉丝 898原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值