深入探讨多线程编程:从0-1为您解释多线程(下)
文章目录
- 6. 死锁
- 6.1 死锁
- 原因
- 6.2 避免死锁的方法
- 加锁顺序一致性。
- 超时机制。
- 死锁检测和解除机制。
6. 死锁
6.1 死锁
原因
系统资源的竞争
:(产生环路)当系统中供多个进程共享的资源数量不足以满足进程的需要时,会引起进程对2资源的竞争而产生死锁。例如,两个进程分别持有资源R1和R2,但进程1申请资源R2,进程2申请资源R1时,两者都会因为所需资源被占用而阻塞。
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>std::timed_mutex resourceR1, resourceR2;bool acquireResource(std::timed_mutex& r, const std::string& name) {std::chrono::milliseconds timeout(5000); // 5秒超时if (r.try_lock_for(timeout)) {std::cout << "Process " << name << " has acquired its resource." << std::endl;return true;}else {std::cout << "Process " << name << " failed to acquire the resource within 5 seconds. Terminating..." << std::endl;return false;}
}void process1() {if (acquireResource(resourceR1, "1")) {// 如果成功获取资源R1,尝试获取资源R2if (!acquireResource(resourceR2, "1")) {// 若获取资源R2失败,解锁资源R1并终止线程resourceR1.unlock();return;}/********************************************************///需要执行的业务逻辑(不会被执行)/********************************************************/resourceR1.unlock();resourceR2.unlock();}
}void process2() {if (acquireResource(resourceR2, "2")) {if (!acquireResource(resourceR1, "2")) {resourceR2.unlock();return;}// 同样,此处的业务逻辑也不会被执行resourceR1.unlock();resourceR2.unlock();}
}int main() {std::thread t1(process1);std::thread t2(process2);t1.join();t2.join();return 0;
}
逻辑错误
:程序逻辑错误可能导致死锁,如死循环或无限等待的情况。例如,在数据交换中,如果一方发送的消息丢失,发送方会等待接收返回信息,而接收方会无限等待接收信息,导致死锁。
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>std::mutex mtx;
std::condition_variable cv1, cv2;
bool messageReceived = false;
bool acknowledgmentSent = false;// 发送线程
void senderThread() {std::cout << "Sender: Sending data...\n";// 假设发送数据(此处省略具体发送逻辑)std::unique_lock<std::mutex> lk(mtx);auto timeout = std::chrono::system_clock::now() + std::chrono::seconds(5);while (!acknowledgmentSent && std::cv_status::timeout == cv1.wait_until(lk, timeout)) {if (std::chrono::system_clock::now() >= timeout) {std::cout << "Sender: Timeout occurred, assuming no acknowledgement received and exiting.\n";break; // 超时后退出循环,不再等待确认}}lk.unlock();
}// 接收线程
void receiverThread() {std::this_thread::sleep_for(std::chrono::seconds(2)); // 假设在此期间消息丢失std::unique_lock<std::mutex> lk(mtx);std::cout << "Receiver: Received data...\n";messageReceived = true;cv2.notify_one(); // 假设这是接收方发送确认的方式// 接收方也会等待发送方确认收到确认信息(这是一个逻辑错误,实际应用中通常不需要)auto timeout = std::chrono::system_clock::now() + std::chrono::seconds(5);while (!messageReceived && std::cv_status::timeout == cv2.wait_until(lk, timeout)) {if (std::chrono::system_clock::now() >= timeout) {std::cout << "Receiver: Timeout occurred, assuming message not delivered and exiting.\n";break; // 超时后退出循环,不再等待消息}}lk.unlock();
}int main() {std::thread t1(senderThread);std::thread t2(receiverThread);t1.join();t2.join();return 0;
}
两秒后
五秒后
不恰当的同步
:在并发编程中,不恰当的同步机制可能导致死锁。例如,多个线程在等待其他线程释放锁时,如果这些线程彼此都持有对方需要的锁,就会导致死锁。
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>std::timed_mutex mtx1, mtx2;void threadFunction1() {if (mtx1.try_lock_for(std::chrono::seconds(5))) {std::cout << "Thread 1: Acquired mtx1\n";// 尝试获取mtx2,如果5秒内未获取成功,则释放mtx1以防止死锁if (!mtx2.try_lock_for(std::chrono::seconds(5))) {mtx1.unlock();std::cout << "Thread 1: Could not acquire mtx2 within 5 seconds, releasing mtx1 to prevent deadlock.\n";return;}std::cout << "Thread 1: Acquired mtx2\n";mtx2.unlock();mtx1.unlock();}else {std::cout << "Thread 1: Could not acquire mtx1 within 5 seconds.\n";}
}void threadFunction2() {if (mtx2.try_lock_for(std::chrono::seconds(5))) {std::cout << "Thread 2: Acquired mtx2\n";if (!mtx1.try_lock_for(std::chrono::seconds(5))) {mtx2.unlock();std::cout << "Thread 2: Could not acquire mtx1 within 5 seconds, releasing mtx2 to prevent deadlock.\n";return;}std::cout << "Thread 2: Acquired mtx1\n";mtx1.unlock();mtx2.unlock();}else {std::cout << "Thread 2: Could not acquire mtx2 within 5 seconds.\n";}
}int main() {std::thread t1(threadFunction1);std::thread t2(threadFunction2);t1.join();t2.join();return 0;
}
6.2 避免死锁的方法
加锁顺序一致性。
#include <iostream>
#include <thread>
#include <mutex>std::mutex mtx1, mtx2;// 定义一个固定的全局锁顺序
const bool lockOrder[] = {true, false}; // 先锁mtx1,后锁mtx2void worker(int id) {if (lockOrder[0]) {mtx1.lock();std::cout << "Thread " << id << ": Acquired mtx1\n";// 在拥有mtx1的情况下尝试获取mtx2mtx2.lock();std::cout << "Thread " << id << ": Acquired mtx2\n";} else {// 如果定义的顺序是先锁mtx2mtx2.lock();std::cout << "Thread " << id << ": Acquired mtx2\n";// 在拥有mtx2的情况下尝试获取mtx1mtx1.lock();std::cout << "Thread " << id << ": Acquired mtx1\n";}// 重要:解锁按照相反的顺序进行mtx2.unlock();mtx1.unlock();// 业务逻辑...
}int main() {std::thread t1(worker, 1);std::thread t2(worker, 2);t1.join();t2.join();return 0;
}
在上述示例中,我们预定义了一个全局的锁获取顺序数组lockOrder,确保所有线程按照同样的顺序(本例中是先获取mtx1再获取mtx2)来获取互斥锁。这样可以防止如下情况:一个线程持有mtx1并等待mtx2,而另一个线程持有mtx2并等待mtx1,从而形成死锁。
请注意,为了避免死锁,不仅在获取锁时需遵循一致的顺序,而且在解锁时也应按照相反的顺序进行。在上面的代码中,无论哪种顺序,我们都是先解锁mtx2,然后再解锁mtx1。这样可以确保在任何时候,已经持有两个锁的线程都能顺利地按顺序释放它们,避免死锁的发生。
超时机制。
以下是一个使用std::timed_mutex的示例,当尝试获取互斥锁时设置一个超时时间,如果在规定时间内没能获取到锁,则线程放弃获取,从而可以避免死锁的发生:
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>std::timed_mutex mtx1, mtx2;void worker(int id) {if (id == 1) {// 线程1尝试获取mtx1if (mtx1.try_lock_for(std::chrono::seconds(5))) {std::cout << "Thread " << id << ": Acquired mtx1\n";// 在持有mtx1的前提下尝试获取mtx2,超时时间为5秒if (mtx2.try_lock_for(std::chrono::seconds(5))) {std::cout << "Thread " << id << ": Acquired mtx2\n";mtx2.unlock();} else {std::cout << "Thread " << id << ": Could not acquire mtx2 within 5 seconds, releasing mtx1.\n";}mtx1.unlock();} else {std::cout << "Thread " << id << ": Could not acquire mtx1 within 5 seconds.\n";}} else if (id == 2) {// 线程2尝试获取mtx2,同样设置5秒超时if (mtx2.try_lock_for(std::chrono::seconds(5))) {std::cout << "Thread " << id << ": Acquired mtx2\n";// 在持有mtx2的前提下尝试获取mtx1,同样设置5秒超时if (mtx1.try_lock_for(std::chrono::seconds(5))) {std::cout << "Thread " << id << ": Acquired mtx1\n";mtx1.unlock();} else {std::cout << "Thread " << id << ": Could not acquire mtx1 within 5 seconds, releasing mtx2.\n";}mtx2.unlock();} else {std::cout << "Thread " << id << ": Could not acquire mtx2 within 5 seconds.\n";}}
}int main() {std::thread t1(worker, 1);std::thread t2(worker, 2);t1.join();t2.join();return 0;
}
在这个示例中,两个线程都试图按顺序获取互斥锁,但是如果在5秒钟内无法获取所需的下一个锁,它们都会释放已经持有的锁并退出相应的操作,从而避免了死锁的发生。
死锁检测和解除机制。
在C++标准库中并没有内置的死锁检测和解除机制,但我们可以通过设计良好的程序逻辑和利用特定的同步原语(如条件变量、互斥量等)来实施自己的死锁检测和解除策略。
// 假设有以下结构表示资源和进程的状态
struct Process {int pid; // 进程IDstd::vector<int> holdingResources; // 当前持有的资源ID集合std::vector<int> requestingResources; // 正在请求的资源ID集合
};struct Resource {int rid; // 资源IDint available; // 当前可用的数量std::map<int, int> allocated; // 已分配给各个进程的资源数量
};// 假设有个全局的数据结构存储所有进程和资源的状态
std::vector<Process> processes;
std::vector<Resource> resources;// 自定义的死锁检测函数(伪代码)
bool detectAndResolveDeadlocks() {// 初始化资源分配图(Resource Allocation Graph, RAG)// ...// 检查是否有循环等待for (auto& p : processes) {// 使用拓扑排序或其他方法检查是否存在环路if (isCycleDetectedInRAG(p)) {// 死锁检测出环,现在需要解除死锁resolveDeadlock(p.pid);return true;}}return false; // 没有发现死锁
}// 解除死锁的策略有很多种,以下是一个简化的版本(仅作示例)
void resolveDeadlock(int pid) {// 可以选择一个进程撤销其部分请求或者抢占它的资源// 例如,选择持有最多资源但请求未满足最多的进程,释放其最少的一个资源Process& victim = getVictimProcess(pid);int resourceToRelease = getResourceToRelease(victim);// 释放资源并重新开始检测releaseResource(victim, resourceToRelease);victim.requestingResources.erase(std::find(victim.requestingResources.begin(), victim.requestingResources.end(), resourceToRelease));
}// ... 其他辅助函数(getVictimProcess, getResourceToRelease, releaseResource等)
在实践中,死锁检测和解除往往涉及到复杂的算法和策略,比如银行家算法等。在C++程序中实现这样的功能通常需要自定义数据结构和算法,并且考虑到并发环境下的安全性,还需要适当使用锁来保护共享数据。
由于C++标准库提供的互斥量和条件变量等工具不具备自动死锁检测和解除功能,开发者需要自行设计和实现适合项目需求的死锁预防、检测及解除方案。在某些高级并发库中,可能提供了更高级别的抽象帮助处理这类问题,但C++标准库本身不直接提供这样的机制。
相关文章:
深入探讨多线程编程:从0-1为您解释多线程(下)
文章目录 6. 死锁6.1 死锁原因 6.2 避免死锁的方法加锁顺序一致性。超时机制。死锁检测和解除机制。 6. 死锁 6.1 死锁 原因 系统资源的竞争:(产生环路)当系统中供多个进程共享的资源数量不足以满足进程的需要时,会引起进程对2…...
深度学习pytorch——减少过拟合的几种方法(持续更新)
1、增加数据集 2、正则化(Regularization) 正则化:得到一个更加简单的模型的方法。 以一个多项式为例: 随着最高次的增加,会得到一个更加复杂模型,模型越复杂就会更好的拟合输入数据的模型(图-1)&#…...
排序第五篇 归并排序
一 简介 归并排序(Merge Sort) 的基本思想是: 首先将待排序文件看成 n n n 个长度为1的有序子文件, 把这些子文件两两归并, 得到 n 2 \frac{n}{2} 2n 个长度为 2 的有序子文件; 然后再把这 n 2 \frac{n}{2} 2n 个有序的子…...
【Win】使用PowerShell和Webhooks轻松发送消息至Microsoft Teams
Microsoft Teams是一款由微软开发的团队协作和通讯工具。如果您对这个名字还不太熟悉,那么现在就是一个了解它的好时机。微软将Teams定位为其之前Skype for Business解决方案的继任者,并且它也提供了与其他基于频道的通讯应用程序(例如Slack、…...
ESCTF-OSINT赛题WP
这你做不出来?check ESCTF{湖北大学_嘉会园食堂} 这个识图可以发现是 淡水渔人码头 但是 osint 你要发现所有信息 聊天记录说国外 同时 提示给了美国 你综合搜索 美国 渔人码头 在美国旧金山的渔人码头(英语:Fisherman’s Wharf)是一个著名旅…...
2024蓝桥杯省赛保奖突击班-Day2-前缀和、差分、尺取_笔记_练习题解
3月25日-课堂笔记 前缀和预处理 O ( n ) \mathcal{O}(n) O(n) s[1] a[1]; for(int i 2; i < n; i)s[i] s[i - 1] a[i];利用前缀和查询区间和 O ( 1 ) O(1) O(1) long long calc(int l, int r) {return l 1 ? s[r] : s[r] - s[l - 1]; }差分序列的求法 c[1] a[…...
C++基础之虚函数(十七)
一.什么是多态 多态是在有继承关系的类中,调用同一个指令(函数),不同对象会有不同行为。 二.什么是虚函数 概念:首先虚函数是存在于类的成员函数中,通过virtual关键字修饰的成员函数叫虚函数。 性质&am…...
快速入门Kotlin①基本语法
前言 23年底读了一遍“Kotlin官方文档”,官方文档大而全,阅读下来,大有裨益。 此系列文章的目的是记录学习进程,同时,若能让读者迅速掌握重点内容并快速上手,那就再好不过了。 函数 带有两个 Int 参数、…...
【理解指针(四)】
文章目录 一、指针数组二、指针数组来模拟二维数组三、字符指针变量注意: 字符串的例子(曾经的一道笔试题) 四、数组指针变量1、什么是数组指针变量2、数组指针怎么初始化 五、二维数组传参的本质六、函数指针1、什么是函数指针变量2、函数的…...
Ribbon简介
目录 一 、概念介绍 1、Ribbon是什么 2、认识负载均衡 2.1 服务器端的负载均衡 2.2 客户端的负载均衡 3、Ribbon工作原理 4、Ribbon的主要组件 IClientConfig ServerList ServerListFilter IRule Iping ILoadBalancer ServerListUpdater 5、Ribbon支持…...
【感悟《剑指offer》典型编程题的极练之路】02字符串篇!
个人主页:秋风起,再归来~ 文章所属专栏:《剑指offer》典型编程题的极练之路 个人格言:悟已往之不谏,知来者犹可追 克心守己,…...
通过 Docker 实现国产数据库 OpenGauss 开发环境搭建
通过 Docker 实现国产数据库 OpenGauss 开发环境搭建 一 前置准备 2.1 下载镜像 docker pull enmotech/opengauss:5.0.1构建镜像的 Dockerfile,方便后期实现个性化定制: FROM ubuntu:22.04 as builderARG TARGETARCHWORKDIR /warehouseRUN set -eux;…...
【Java】LinkedList模拟实现
目录 整体框架IMyLinkedList接口IndexNotLegalException异常类MyLinkedList类成员变量(节点信息)addFirst(头插)addLast(尾插)在指定位置插入数据判断是否存在移除第一个相等的节点移除所有相等的节点链表的长度打印链表释放回收链表 整体框架 IMyLinkedList接口 这个接口用来…...
ubuntu下mysql常用命令
1. 登录数据库 mysql -u root -p 2.创建数据库 create database 数据库名字 mysql> create database yourdb; Query OK, 1 row affected (0.03 sec)3.显示数据库 show databases; 实操结果如下 mysql> show databases; -------------------- | Database | ---…...
燃气官网安全运行监测系统-阀井燃气监测仪-旭华智能
近年来,燃气爆炸事故频发,造成了重大人员伤亡和财产损失。这也再次为我们敲响警钟,燃气是我们日常生活中不可或缺的能源,但其潜在的危险性也是不容小觑。因此在重要节点加装燃气阀井气体监测仪,并将数据上传到系统平台…...
vue 文件预览(docx、.xlsx、pdf)
1.ifream <iframe src"" ></iframe> 注: src里面是文件地址 2.vue-office 支持vue2和vue3提供docx、.xlsx、pdf多种文档的在线预览方案 2.1安装 #docx文档预览组件 npm install vue-office/docx vue-demi#excel文档预览组件 npm install vue-office…...
云架构(二) 大使模式
Ambassador pattern (https://learn.microsoft.com/en-us/azure/architecture/patterns/ambassador) 简单描述 创建一个助手服务,这个服务代表消费服务或者应用程序发送网络请求。大使服务可以看做是与客户机同一个位置的进程外代理。 这种…...
.NET Path类库的特殊方法
在.NET中Path类库是非常常用的一个类库,包含很多我们常用的方法,常用的方法这里就不详细说明了,这里记录下几个非常规的方法。 获取随机文件名: //将返回随机的文件名Console.WriteLine(Path.GetRandomFileName()); 获取禁止在路…...
【JVM】JVM常用性能调优参数详细介绍
JVM常用性能调优参数详细介绍 一、何时进行JVM调优二、性能调优三、JVM调优的基本原则四、JVM调优目标五、JVM调优的步骤六、JVM参数七、JVM参数解析及调优八、JVM参数使用手册8.1 内存相关8.2 GC策略相关8.3 GC日志相关8.4 异常相关8.5 问题定位及优化相关九、参考文档一、何时…...
React中的受控组件与非受控组件
受控组件与非受控组件 受控组件 组件(input, select)的状态与state的值绑定,组件的状态全程响应外部数据 class TestComponent extends React.Component {constructor (props) {super(props);this.state { username: lindaidai };}render () {return <input …...
uniapp实现u-datetime-picker时间选择器的默认日期定位,解决default-value不生效问题
uniapp实现u-datetime-picker,设置默认定位日期,解决default-value不生效问题 想实现的效果是点开时间选择器默认显示当前日期,而不是该选择器最早的日期 给选择器添加ref属性,如下: <u-datetime-picker :show&q…...
react native 使用ScrollView实现下拉更新,上拉加载更多
在React Native中,要实现下拉更新和上拉加载更多的功能,你需要自定义ScrollView组件,监听滚动事件并根据滚动的位置来判断何时触发更新和加载更多的操作。以下是一个基本的实现思路: 监听滚动事件:使用ScrollView的on…...
vue2完结
笔记 关于不同版本的Vue: 1.vue.js与vue.runtime.xxx.js的区别:(1)vue.js是完整版的Vue,包含:核心功能模板解析器(2)vue.runtime.xxx.js是运行版本的Vue,只包含核心功能,没有模板解析器 2.因为…...
前端网页之间传递参数
在多页面应用中,我们可能面临着前端页面之间传递参数的情况,在一个页面获取到一些参数信息后,到另一个页面去进行后续处理,需要将前一个页面得到的一些参数带到第二个页面。当参数较少时,可以在跳转第二个页面时通过se…...
【常见面试题】Golang中,协程数最多可以开多少个?
参考: Goroutine 究竟可以开多少? 一、先说结论: 能开多少个协程,取决于单个协程处理方法所占用的CPU和内存资源(也就是看你计算机运行的应用程序的具体代码逻辑)。 二、具体来说: 如果是C…...
RabbitMQ基础笔记
视频链接:【黑马程序员RabbitMQ入门到实战教程】 文章目录 1.初识MQ1.1.同步调用1.2.异步调用1.3.技术选型 2.RabbitMQ2.1.安装2.1.1 Docker2.1.1 Linux2.1.1 Windows 2.2.收发消息2.2.1.交换机2.2.2.队列2.2.3.绑定关系2.2.4.发送消息 2.3.数据隔离2.3.1.用户管理2…...
大型项目管理神器:掌握yarn monorepo的安装和使用
I. 引言 在当今的前端开发中,由于项目规模的不断增长和多团队协同,Monorepo成为了越来越流行的开发模式。Monorepo指的是将多个相关项目或者模块打包在一起的软件开发模式,它可以让开发人员更好地组织管理代码,减少重复的代码&am…...
算法打卡day28|贪心算法篇02|Leetcode 122.买卖股票的最佳时机 II、55. 跳跃游戏、45.跳跃游戏 II
算法题 Leetcode 122.买卖股票的最佳时机 II 题目链接:122.买卖股票的最佳时机 II 大佬视频讲解:买卖股票的最佳时机 II视频讲解 个人思路 因为只有一只股票,且两天作一个交易单元,那每次只收集正利润就可以最终最多可以获取的利润…...
2013年认证杯SPSSPRO杯数学建模A题(第一阶段)护岸框架全过程文档及程序
2013年认证杯SPSSPRO杯数学建模 A题 护岸框架 原题再现: 在江河中,堤岸、江心洲的迎水区域被水流长期冲刷侵蚀。在河道整治工程中,需要在受侵蚀严重的部位设置一些人工设施,以减弱水流的冲刷,促进该处泥沙的淤积&…...
【3】3道链表力扣题:删除链表中的节点、反转链表、判断一个链表是否有环
3道链表力扣题 一、删除链表中的节点🌏 题目链接📕 示例🍀 分析💻 代码 二、反转链表🌏 题目链接📕 示例🍀 分析① 递归② 迭代 三、判断一个链表是否有环🌏 题目链接📕 …...
在百度网站备案查询上显示未备案是什么意思/百度竞价排名利弊
基于命令行执行jar的外放配置文件的执行方法 配置文件在工程目录存放位置: src/conf/application.properties打包生成sproutgis-exec.jar文件 拷贝到/usr/test目录下,目录内容: #--------------------------------------- conf/application.properties…...
大连建站系统模板/外贸推广具体是做什么
Redis大数据处理:如何高效地利用Redis存储海量数据 Redis作为一款高性能的NoSQL内存数据库,被广泛应用于各种领域。当需要处理海量数据时,Redis也有许多优秀的解决方案。本文将介绍几种常见的Redis大数据处理方法,并给出相应的代码示例。 分布式存储Redis Cluster是Redis提…...
网站后台做图片尺寸是多大/抖音代运营公司
一、HTTP服务器的基本知识 1、Node如何向开发者呈现HTTP请求 Node中的http模块提供了HTTP服务器和客户端接口: var http require(http); 创建HTTP服务器要调用http.createServer()函数。它只有一个函数,是个回调函数,服务器每次收到HTTP请求…...
网站推广的10种方法/旅游营销推广方案
Spring Web MVC是一种基于Java的实现了Web MVC设计模式的请求驱动类型的轻量级Web框架,即使用了MVC架构模式的思想,将web层进行职责解耦,基于请求驱动指的就是使用请求-响应模型,框架的目的就是帮助我们简化开发,Sprin…...
党校网站建设栏目内容/怎么做一个网页
本篇文章的主要内容是用PHP实现插入排序,简单却经典的一道算法题,不知你是否记得了,快随小编一起回顾一下吧。插入排序基本思路:将数组分为两个区(已排序区和未排序区),假定数组的第一个元素处于已排序区, …...
重要新闻头条/广东seo网络培训
JVM有哪些垃圾回收器?是怎么工作的?什么是STW?它都发生在哪些阶段?什么是三色标记?如何解决错标记和漏标记的问题?为什么要设计这么多的垃圾回收器? STW:Stop-The-Word。是在垃圾回收算法执行过程中&…...