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

深入探讨多线程编程:从0-1为您解释多线程(下)

文章目录

  • 6. 死锁
    • 6.1 死锁
      • 原因
    • 6.2 避免死锁的方法
      • 加锁顺序一致性。
      • 超时机制。
      • 死锁检测和解除机制。

6. 死锁

6.1 死锁

原因

  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;
}

在这里插入图片描述
在这里插入图片描述

  1. 逻辑错误:程序逻辑错误可能导致死锁,如死循环或无限等待的情况。例如,在数据交换中,如果一方发送的消息丢失,发送方会等待接收返回信息,而接收方会无限等待接收信息,导致死锁。
#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;
}

在这里插入图片描述
两秒后
在这里插入图片描述
五秒后
在这里插入图片描述

  1. 不恰当的同步:在并发编程中,不恰当的同步机制可能导致死锁。例如,多个线程在等待其他线程释放锁时,如果这些线程彼此都持有对方需要的锁,就会导致死锁。
#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 死锁 原因 系统资源的竞争&#xff1a;&#xff08;产生环路&#xff09;当系统中供多个进程共享的资源数量不足以满足进程的需要时&#xff0c;会引起进程对2…...

深度学习pytorch——减少过拟合的几种方法(持续更新)

1、增加数据集 2、正则化(Regularization) 正则化&#xff1a;得到一个更加简单的模型的方法。 以一个多项式为例&#xff1a; 随着最高次的增加&#xff0c;会得到一个更加复杂模型&#xff0c;模型越复杂就会更好的拟合输入数据的模型&#xff08;图-1&#xff09;&#…...

排序第五篇 归并排序

一 简介 归并排序(Merge Sort) 的基本思想是&#xff1a; 首先将待排序文件看成 n n n 个长度为1的有序子文件&#xff0c; 把这些子文件两两归并&#xff0c; 得到 n 2 \frac{n}{2} 2n​ 个长度为 2 的有序子文件&#xff1b; 然后再把这 n 2 \frac{n}{2} 2n​ 个有序的子…...

【Win】使用PowerShell和Webhooks轻松发送消息至Microsoft Teams

Microsoft Teams是一款由微软开发的团队协作和通讯工具。如果您对这个名字还不太熟悉&#xff0c;那么现在就是一个了解它的好时机。微软将Teams定位为其之前Skype for Business解决方案的继任者&#xff0c;并且它也提供了与其他基于频道的通讯应用程序&#xff08;例如Slack、…...

ESCTF-OSINT赛题WP

这你做不出来?check ESCTF{湖北大学_嘉会园食堂} 这个识图可以发现是 淡水渔人码头 但是 osint 你要发现所有信息 聊天记录说国外 同时 提示给了美国 你综合搜索 美国 渔人码头 在美国旧金山的渔人码头&#xff08;英语&#xff1a;Fisherman’s Wharf&#xff09;是一个著名旅…...

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++基础之虚函数(十七)

一.什么是多态 多态是在有继承关系的类中&#xff0c;调用同一个指令&#xff08;函数&#xff09;&#xff0c;不同对象会有不同行为。 二.什么是虚函数 概念&#xff1a;首先虚函数是存在于类的成员函数中&#xff0c;通过virtual关键字修饰的成员函数叫虚函数。 性质&am…...

快速入门Kotlin①基本语法

前言 23年底读了一遍“Kotlin官方文档”&#xff0c;官方文档大而全&#xff0c;阅读下来&#xff0c;大有裨益。 此系列文章的目的是记录学习进程&#xff0c;同时&#xff0c;若能让读者迅速掌握重点内容并快速上手&#xff0c;那就再好不过了。 函数 带有两个 Int 参数、…...

【理解指针(四)】

文章目录 一、指针数组二、指针数组来模拟二维数组三、字符指针变量注意&#xff1a; 字符串的例子&#xff08;曾经的一道笔试题&#xff09; 四、数组指针变量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字符串篇!

​ 个人主页&#xff1a;秋风起&#xff0c;再归来~ 文章所属专栏&#xff1a;《剑指offer》典型编程题的极练之路 ​​​​​​ 个人格言&#xff1a;悟已往之不谏&#xff0c;知来者犹可追 克心守己&#xff0c…...

通过 Docker 实现国产数据库 OpenGauss 开发环境搭建

通过 Docker 实现国产数据库 OpenGauss 开发环境搭建 一 前置准备 2.1 下载镜像 docker pull enmotech/opengauss:5.0.1构建镜像的 Dockerfile&#xff0c;方便后期实现个性化定制&#xff1a; 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 | ---…...

燃气官网安全运行监测系统-阀井燃气监测仪-旭华智能

近年来&#xff0c;燃气爆炸事故频发&#xff0c;造成了重大人员伤亡和财产损失。这也再次为我们敲响警钟&#xff0c;燃气是我们日常生活中不可或缺的能源&#xff0c;但其潜在的危险性也是不容小觑。因此在重要节点加装燃气阀井气体监测仪&#xff0c;并将数据上传到系统平台…...

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 &#xff08;https://learn.microsoft.com/en-us/azure/architecture/patterns/ambassador&#xff09; 简单描述 创建一个助手服务&#xff0c;这个服务代表消费服务或者应用程序发送网络请求。大使服务可以看做是与客户机同一个位置的进程外代理。 这种…...

.NET Path类库的特殊方法

在.NET中Path类库是非常常用的一个类库&#xff0c;包含很多我们常用的方法&#xff0c;常用的方法这里就不详细说明了&#xff0c;这里记录下几个非常规的方法。 获取随机文件名&#xff1a; //将返回随机的文件名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的值绑定&#xff0c;组件的状态全程响应外部数据 class TestComponent extends React.Component {constructor (props) {super(props);this.state { username: lindaidai };}render () {return <input …...

KubeSphere 容器平台高可用:环境搭建与可视化操作指南

Linux_k8s篇 欢迎来到Linux的世界&#xff0c;看笔记好好学多敲多打&#xff0c;每个人都是大神&#xff01; 题目&#xff1a;KubeSphere 容器平台高可用&#xff1a;环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...

深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录

ASP.NET Core 是一个跨平台的开源框架&#xff0c;用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录&#xff0c;以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...

树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法

树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源&#xff1a; http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作&#xff0c;无需更改相机配置。但是&#xff0c;一…...

渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止

<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet&#xff1a; https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...

【SQL学习笔记1】增删改查+多表连接全解析(内附SQL免费在线练习工具)

可以使用Sqliteviz这个网站免费编写sql语句&#xff0c;它能够让用户直接在浏览器内练习SQL的语法&#xff0c;不需要安装任何软件。 链接如下&#xff1a; sqliteviz 注意&#xff1a; 在转写SQL语法时&#xff0c;关键字之间有一个特定的顺序&#xff0c;这个顺序会影响到…...

Java入门学习详细版(一)

大家好&#xff0c;Java 学习是一个系统学习的过程&#xff0c;核心原则就是“理论 实践 坚持”&#xff0c;并且需循序渐进&#xff0c;不可过于着急&#xff0c;本篇文章推出的这份详细入门学习资料将带大家从零基础开始&#xff0c;逐步掌握 Java 的核心概念和编程技能。 …...

Python 包管理器 uv 介绍

Python 包管理器 uv 全面介绍 uv 是由 Astral&#xff08;热门工具 Ruff 的开发者&#xff09;推出的下一代高性能 Python 包管理器和构建工具&#xff0c;用 Rust 编写。它旨在解决传统工具&#xff08;如 pip、virtualenv、pip-tools&#xff09;的性能瓶颈&#xff0c;同时…...

Go 语言并发编程基础:无缓冲与有缓冲通道

在上一章节中&#xff0c;我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道&#xff0c;它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好&#xff0…...

纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join

纯 Java 项目&#xff08;非 SpringBoot&#xff09;集成 Mybatis-Plus 和 Mybatis-Plus-Join 1、依赖1.1、依赖版本1.2、pom.xml 2、代码2.1、SqlSession 构造器2.2、MybatisPlus代码生成器2.3、获取 config.yml 配置2.3.1、config.yml2.3.2、项目配置类 2.4、ftl 模板2.4.1、…...

Web中间件--tomcat学习

Web中间件–tomcat Java虚拟机详解 什么是JAVA虚拟机 Java虚拟机是一个抽象的计算机&#xff0c;它可以执行Java字节码。Java虚拟机是Java平台的一部分&#xff0c;Java平台由Java语言、Java API和Java虚拟机组成。Java虚拟机的主要作用是将Java字节码转换为机器代码&#x…...