死锁Deadlock

news2024/12/16 16:05:59

定义

        死锁是指两个或多个线程互相持有对方所需的资源,从而导致它们无法继续执行的情况。如下图所示,现有两个线程,分别是线程A及线程B,线程A持有锁A,线程B持有锁B。此时线程A想获取锁B,但锁B需等到线程B的结束才能解锁;而线程B想获取锁A,但锁A需等到线程A的结束才能解锁,这样就造成了线程A等线程B,线程B等线程A,从而出现死锁。

图解两线程死锁现象

c++死锁示例 

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

std::mutex mutexA;
std::mutex mutexB;

void ThreadA()
{
    std::unique_lock<std::mutex> lockA(mutexA);
    std::this_thread::sleep_for(std::chrono::milliseconds(100));  // 为了增加发生死锁的概率,让线程A先获取锁A再休眠一段时间
    std::cout << "Thread A acquired mutex A" << std::endl;

    std::unique_lock<std::mutex> lockB(mutexB);
    std::cout << "Thread A acquired mutex B" << std::endl;

    // 执行操作...

    lockB.unlock();
    lockA.unlock();
}

void ThreadB()
{
    std::unique_lock<std::mutex> lockB(mutexB);
    std::cout << "Thread B acquired mutex B" << std::endl;

    std::unique_lock<std::mutex> lockA(mutexA);
    std::cout << "Thread B acquired mutex A" << std::endl;

    // 执行操作...

    lockA.unlock();
    lockB.unlock();
}

int main()
{
    std::thread threadA(ThreadA);
    std::thread threadB(ThreadB);

    threadA.join();
    threadB.join();

    return 0;
}

输出结果:(程序中止崩溃)

分析: 

        在上述代码中,有两个线程(ThreadA和ThreadB),它们都试图以不同的顺序获取两个互斥锁(mutexA和mutexB)。如果ThreadA先获取了mutexA,然后尝试获取mutexB,而ThreadB先获取了mutexB,然后尝试获取mutexA,那么就会发生死锁。

        当ThreadA获取了mutexA后,它会休眠一段时间。在此期间,ThreadB获取了mutexB。接着,ThreadA试图获取mutexB时会被阻塞,因为ThreadB持有mutexB。同时,ThreadB也试图获取mutexA时会被阻塞,因为ThreadA持有mutexA。这样,两个线程都无法继续执行,导致程序陷入死锁状态。

        需要注意的是,死锁不一定会在每次运行时都发生,它取决于线程的调度和执行顺序。如果在实际应用中遇到死锁问题,可以通过合理的锁顺序、避免嵌套锁、使用死锁检测等方法来预防和解决死锁。

c++解决死锁

修改上述代码,使用std::lock函数来避免死锁:

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

std::mutex mutexA;
std::mutex mutexB;

void ThreadA()
{
    std::unique_lock<std::mutex> lockA(mutexA, std::defer_lock);
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    std::cout << "Thread A acquired mutex A" << std::endl;

    std::unique_lock<std::mutex> lockB(mutexB, std::defer_lock);
    std::cout << "Thread A acquired mutex B" << std::endl;

    std::lock(lockA, lockB);  // 使用std::lock同时获取两个锁

    // 执行操作...

    lockB.unlock();
    lockA.unlock();
}

void ThreadB()
{
    std::unique_lock<std::mutex> lockB(mutexB, std::defer_lock);
    std::cout << "Thread B acquired mutex B" << std::endl;

    std::unique_lock<std::mutex> lockA(mutexA, std::defer_lock);
    std::cout << "Thread B acquired mutex A" << std::endl;

    std::lock(lockA, lockB);  // 使用std::lock同时获取两个锁

    // 执行操作...

    lockA.unlock();
    lockB.unlock();
}

int main()
{
    std::thread threadA(ThreadA);
    std::thread threadB(ThreadB);

    threadA.join();
    threadB.join();

    return 0;
}

输出结果:(正确结果)

分析:

        在修改后的代码中,使用std::defer_lock参数来延迟锁的获取,而不是在构造std::unique_lock对象时立即获取锁。然后,在需要同时获取两个锁的地方,使用std::lock函数来获取锁,这样可以避免死锁发生。

        需要注意的是,虽然上述方法可以解决死锁问题,但在实际编程中,应尽量避免复杂的锁依赖关系和嵌套锁的使用,以减少死锁的可能性。

产生死锁的原因

竞争不可抢占资源(互斥资源)
线程推进顺序不当

产生死锁的四个必要条件

  1. 互斥条件(Mutual Exclusion):至少有一个资源同时只能被一个进程或线程占用,即在一段时间内只能有一个进程或线程访问该资源。

  2. 请求与保持条件(Hold and Wait):进程或线程在持有至少一个资源的同时,又请求其他进程或线程所持有的资源。

  3. 不可剥夺条件(No Preemption):已经分配给一个进程或线程的资源不能被强制性地剥夺,只能由持有资源的进程或线程显式地释放。

  4. 循环等待条件(Circular Wait):存在一个进程或线程的资源请求序列,使得每个进程或线程都在等待下一个进程或线程所持有的资源。

当这四个条件同时满足时,就可能发生死锁。

例如,考虑以下场景:

  • 进程A持有资源X,并请求资源Y。
  • 进程B持有资源Y,并请求资源X。

        如果进程A和进程B同时运行,并且它们按照上述顺序获取和释放资源,那么就会出现死锁。进程A持有资源X,进程B持有资源Y,但它们互相需要对方持有的资源才能继续执行,导致两个进程都无法继续执行下去。

处理死锁的方法

(1)预防死锁(破坏四条件)

  1. 破坏互斥条件:例如,对于某些资源,引入共享访问的机制,使得多个进程或线程可以同时访问资源。

  2. 破坏请求与保持条件:要求进程在请求资源之前释放已经持有的资源,或者一次性请求所有需要的资源。

  3. 破坏不可剥夺条件:引入资源预约和强制剥夺机制,以确保资源可以被其他进程或线程使用。

  4. 破坏循环等待条件:通过定义资源的线性顺序,要求进程按照相同的顺序请求资源,从而避免循环等待。

(2)避免死锁(银行家算法)

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1140631.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机网络之数据链路层(全)

[复习提示] 王道&#xff1a;本章是历年考试中考查的重点。要求在了解数据链路层基本概念和功能的基础上&#xff0c;重点掌握滑动窗口机制、三种可靠传输协议、各种MAC协议、HDLC协议和PPP协议&#xff0c;特别是CSMA/CD协议和以太网帧格式&#xff0c;以及局域网的争用期和最…

C++学习笔记之三(标准库、标准模板库、vector类)

C 1、C标准库2、C标准模板库2.1、vector2.1.1、vector与array2.1.2、vector与函数对象2.1.3、vector与迭代器 1、C标准库 C C C标准库指的是标准程序库( S t a n d a r d Standard Standard L i b a r a y Libaray Libaray)&#xff0c;它定义了十个大类&#xff0c;其中包括…

只有向内生长,才能向外绽放——中国人民大学与加拿大女王大学金融硕士

这个时代&#xff0c;产业迭代、竞争愈发激烈、世界局势变幻等这些都是我们无法控制的事。我们能做到的是不断地努力提升自己&#xff0c;成为所在领域的佼佼者。人的成长之路&#xff0c;就像是一场循序渐进的蜕变。不断努力是让自己逐步增值的过程。只有向内生长&#xff0c;…

世界经济论坛:ChatGPT等生成式AI,对全球23%岗位产生巨大影响

世界经济论坛与全球最大上市咨询公司之一埃森哲合作&#xff0c;联合发布了《未来工作&#xff1a;大语言模型与就业》白皮书。 世界经济论坛表示&#xff0c;随着ChatGPT、Midjourney、Github Copilot等生成式AI的飞速发展&#xff0c;对全球经济和劳动市场产生巨大影响。未来…

手写Vue渲染器render函数

使用js对象来描述UI更加的灵活。“这种对象”在vue框架中被称为虚拟DOM&#xff0c;渲染函数内部可以创建虚拟DOM&#xff0c;然后vue.js可以将其内容进行渲染。 1.渲染器的介绍 渲染器的作用就是把虚拟DOM渲染为真实DOM 思考下&#xff0c;我们有一个虚拟 DOM&#xff0c;如…

js数组的常用方法

目录 1. 添加/删除元素 2. 查找和遍历 3. 转换 4. 排序 5. 其他 JavaScript中的数组是一种用于存储多个值的对象。数组提供了一系列的方法来进行操作&#xff0c;包括添加、删除、遍历和搜索等。下面列举了一些JavaScript数组的常用方法&#xff1a; 1. 添加/删除元素 pu…

C指针 --- 进阶

目录 1. 字符指针 1.1. 一般使用 1.2. 另一种使用 2. 指针数组 3. 数组指针 3.1. 数组指针 3.2. 数组名和&数组名 3.3. 数组指针的用处 1. 传递一个数组 2. 传递数组首元素的地址 3. 数组指针处理一维数组 4. 数组指针处理二维数组 4. 数组传参和指针传参 4.1…

PHP危险函数

PHP危险函数 文章目录 PHP危险函数PHP 代码执行函数eval 语句assert()语句preg_replace()函数正则表达式里修饰符 回调函数call_user_func()函数array_map()函数 OS命令执行函数system()函数exec()函数shell_exec()函数passthru() 函数popen 函数反引号 实列 通过构造函数可以执…

杂牌行车记录仪特殊AVI结构恢复案例

最近遇到一个杂牌的行车记录仪需要恢复数据&#xff0c;其使用AVI格式&#xff0c;但是在扫描恢复的过程中却发现厂家对其AVI结构进行了“魔改”致程序无法正常识别 故障存储:16G SD卡 fat32文件系统 故障现象: 16G的SD卡&#xff0c;在发生事故后客户尝试自行接到手机上读…

小红书素人铺量推广费用是多少?

小红书作为一个拥有庞大用户群体的社交电商平台&#xff0c;在品牌营销和产品推广方面具有巨大的潜力。其中&#xff0c;素人铺量推广作为一种常见的推广方式&#xff0c;被越来越多的品牌和商家所采用。但是&#xff0c;对于很多刚刚接触小红书推广的人来说&#xff0c;素人铺…

二维码智慧门牌管理系统升级解决方案:一级属性 二级属性

文章目录 前言一、什么是智慧门牌管理系统&#xff1f;二、一级属性 vs. 二级属性三、升级中的实践意义 前言 在本文中&#xff0c;我们将深入探讨二维码智慧门牌管理系统的升级解决方案&#xff0c;特别聚焦于一级属性和二级属性的关键概念。我们将详细解释这些概念&#xff…

如何做好建筑翻译呢

近年来&#xff0c;随着跨国工程项目增加&#xff0c;建筑翻译也越来越受到重视。尤其是建筑图纸翻译在工程设计、规划和施工等方面都具有重要意义。那么&#xff0c;如何做好建筑翻译呢&#xff0c;建筑工程翻译哪个比较正规&#xff1f; 在建筑行业日新月异的发展中&#xff…

项目进度延误,危机管理5大注意事项

项目延误危机管理的重要性是不可忽视的。项目延误可能会导致资源浪费、成本增加、客户不满、信誉受损等一系列问题&#xff0c;严重影响项目的成功与效益。因此&#xff0c;有效地进行项目延误危机管理是至关重要的&#xff0c;一般主要是从以下5个方面进行管理&#xff1a; 1、…

python yield用法

用法和ts中的差不多 def test():i[1,2,3,4]for x in i:yield x for j in test():print(j) 使用的时候就会返回一次值 实际使用中可以这样使用&#xff0c;例如我把df按照半小时进行分割&#xff0c;然后分别取处理每半小时的数据 def test():final_df[raw_timestamp] pd.t…

js使用构造函数的注意点?

目录 前言 使用构造函数的注意点 1. 使用 new 关键字 用法 代码示例 理解 2. 构造函数首字母大写 用法 代码示例 理解 3. 在构造函数中返回对象 用法 代码示例 理解 4. 使用原型链 用法 代码示例 理解 总结 前言 JavaScript 是一种基于原型的语言&#xff…

【C#】LIMS实验室信息管理系统源码

一、系统概述 LIMS(Laboratory Information Management System)即实验室信息管理系统,是通过对样品检验流程、分析数据及报告、实验室资源和客户信息等要素的综合管理,按照标准化实验室管理规范,建立符合实验室业务流程的质量体系,实现实验室信息化管理。是实验室提高分析水平…

serverSocket编程DEMO

目录 0、jar包依赖 1、ServerSocket服务端主程序&#xff1a; 2、任务执行类 3、tomcatJdbcPool&#xff1a; 4、客户端&#xff1a; 0、jar包依赖 1、ServerSocket服务端主程序&#xff1a; package socket;import java.net.ServerSocket; import java.net.Socket; impo…

python try-except捕获异常的方法

python try-except捕获异常的方法 执行过程 1、是从try下的代码块1开始执行。 2、如果有异常抛出&#xff0c;异常将被捕获&#xff0c;直接跳转并执行except下的代码块2。、如果代码块1一切正常&#xff0c;没有异常抛出&#xff0c;代码块2就不会执行。 也就是说&#xff…

大语言模型助力审计问题自动定性

国家审计作为以权力监督制约权力的一项制度安排&#xff0c;是党和国家监督体系的重要组成部分&#xff0c;是国家治理大系统中一个内生的具有预防、揭示和抵御功能的“免疫系统”&#xff0c;是国家治理的基石和重要保障&#xff0c;在推进“全面建成小康社会、全面深化改革、…

Lvs+Nginx+NDS

什么是&#xff1f;为什么&#xff1f;需要负载均衡 一个网站在创建初期&#xff0c;一般来说都是只有一台服务器对用户提供服务 ​ 从图里可以看出&#xff0c;用户经过互联网直接连接了后端服务器&#xff0c;如果这台服务器什么时候突然 GG 了&#xff0c;用户将无法访问这…