【线程池项目(四)】项目的死锁问题分析和资源回收机制的改善

在上一篇 【线程池项目(三)】线程池CACHED模式的实现中我们大概说了说cached模式的基本实现,对于多线程编程,我们需要考虑的问题也较于单线程更多、更复杂,经常存在线程同步、资源竞争等复杂的并发控制问题,因此上篇的文末留下了三个问题🤔🤔🤔
在这里插入图片描述
这是本篇博客关注的第一个死锁问题,另一个就是在跨平台编译(Ubuntu20.04)时,由于平台差异化而引起的出现的死锁性问题,下面我们来详细的剖析一下

如果需要与本篇博客完全匹配的实现代码,也可以在 我的gitee 上下载对应的源码

项目经历——基于C++新特性以及模板编程实现的线程池

    • 一、三种状态的可能性死锁问题🧐🧐🧐
    • 二、对应的解决方案😮😮😮
    • 三、资源回收机制的改良😦😦😦
    • 四、平台差异化的死锁性问题(没有发生,代码竟然能跑🤣🤣)
    • 五、对应的解决方案🥹🥹🥹
    • 六、总结😩😩😩

一、三种状态的可能性死锁问题🧐🧐🧐

我们先来看看前两种情况

  1. 🤔 当线程数多余任务队列中的任务数或者是任务队列为空时,在一定的时间内,肯定会有某个或者某些线程处于空闲的wait状态等待任务的到来,如果这时用户准备结束主程序(即执行线程池的析构函数),将isPoolRunning置为false,并进行notEmpty.notify_all()通知,等待在wait上的线程被唤醒往下执行,在下面的if判断语句中满足结束条件,正常的回收线程;cached模式通过自己的回收机制也能正常的回收线程,不会造成死锁问题的发生

  2. 🤔 当某个线程正在执行任务exec且还未执行完时,用户准备结束主程序(即执行线程池的析构函数),析构函数中的语句对正在执行任务的线程没有任何影响,当线程执行任务完成且更新完任务执行完的时间后,同样也能够通过while()的判断条件正常的退出,进而被回收,也不会发生死锁问题

这是第三种情况

  1. 🤔 当某个线程执行任务完成,且用户准备结束主程序(即调用线程池的析构函数)时,线程在isPoolRunning被主程序置为false之前,执行完任务的线程已经进入了外边的while循环,并且还没有执行到fixed模式下的wait,这时主线程往下执行析构函数,通知等待的线程,并等待所有线程被回收,之后执行完任务的线程再往下执行至notEmpyt.wait(),显然现在线程池中只有一个线程等待被唤醒,而用户所在的主线程也阻塞在exitCond_wait()上,出现了死锁现象

在这里插入图片描述

🫠以下是正常结束的执行结果(具有偶然性)

在这里插入图片描述

🫠这是发生死锁问题的执行结果,有一个任务线程没有被回收,主程序线程也被无限期等待

在这里插入图片描述

二、对应的解决方案😮😮😮

来看看修改后的代码
在这里插入图片描述

  • 当主线程执行析构函数时先抢到锁,线程池中的线程会在定义unique_lock上阻塞,进而主线程继续执行通知正在wait的线程,而无论是正在执行任务的线程,还是wait状态的线程,都不会出现死锁问题,因为执行任务的线程不影响,而wait状态的线程会在被唤醒后,也会在if条件下break,正常的被回收
  • 当线程池里面的线程先抢到锁时,主线程线程会阻塞在unique_lock上,线程池里的线程往下执行,无论是执行到wait语句,还是直接获取到任务exec,主程序线程总是会比线程池里的线程慢一步,后执行notify_all()操作来唤醒等待的线程,因此也不会发生死锁问题

三、资源回收机制的改良😦😦😦

前面我们对于线程池的析构函数进行了优化,完善了线程池的资源回收机制,但还是有问题没考虑到,我们资源回收关注的点仅仅是线程,并没有过多的看到任务的具体执行情况,即当线程池要析构、结束时,如果还有用户提交的任务没有执行完,该怎么办 ? 当然,我们很容易想到,既然线程池都已经结束了,那里边正在执行的任务也势必会直接结束。但这仅仅是对我们自己而言,要实现一个完整、通用的系统,我们肯定是需要等待任务执行完毕才行,我们更改以下测试程序,看看会发生什么
在这里插入图片描述
下面是执行结果
在这里插入图片描述
看看,用户如果不需要获取任务执行完毕的返回值(即调用get()方法阻塞),只是让线程池执行任务,他辛辛苦苦提交的任务根本就没有被执行,然而主线程就直接退出了!!!因此我们仍然需要修改我们之前优化过的代码!!!

在这里插入图片描述
图例不太清除,这里就不介绍了,可以在 我的gitee 上下载对应的源码进行对比分析


ubuntu20.04编译动态库g++ threadpool.cpp -fPIC -shared libpool.so -std=c++17
动态库搜索路径/usr/local/lib
头文件搜索路径usr/local/include
测试代码执行命令g++ main.cpp -o a -lpool -lpthread -std=c++17


四、平台差异化的死锁性问题(没有发生,代码竟然能跑🤣🤣)

Visual Stdio 2022 的 condition_variable 底层源码,完善了条件变量的析构函数,正常释放

在这里插入图片描述
Linux系统上 condition_variable 的底层源码,析构函数没有做任何操作
在这里插入图片描述

实际上,我在Linux上运行该项目时,并没有遇到老师提到的这些情况😵😵,这可能是因为我的配置存在问题,或者是因为在后续版本中开发者大佬们解决了这些问题。

五、对应的解决方案🥹🥹🥹

使用Linux环境下的gdb调试,通过gdb attach PID,调试当前正在运行的程序,ps -u可以查看当前正在执行的进程任务,info threads查看所有线程的状态以及当前正在哪个关注线程,thread n关注第n个线程,bt打印该线程执行函数的堆栈信息,进行问题分析。

六、总结😩😩😩

以上内容涉及了《线程池项目(四)》项目中死锁问题的分析以及资源回收机制的改进。关于项目后续采用packaged_task和future进行的二次开发和重构的详情就不介绍了,其源码可在 我的gitee 上找到。这也是本系列《手写线程池》的最后一篇🔚 。未来,我将与大家分享更多项目。我知识有限,博客中还有很多地方需要改进,出现问题希望大佬们能够及时指正,感谢大家的支持!🌻🌻🌻

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

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

相关文章

七、函数式编程

一、概念 函数式编程通过使用函数,来将值转换成抽象单元,接着用于构建软件系统。 命令式编程和函数式编程的区别? 命令式编程往往是建立在直接操作和检查程序状态之上的。 函数式,倾向于把程序拆分,并抽象成多个函数组装回去。 1、高阶函数 函数可以作为返回值(给了一些…

【Java程序设计】【C00276】基于Springboot的就业信息管理系统(有论文)

基于Springboot的就业信息管理系统(有论文) 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于Springboot的就业信息管理系统 本系统分为前台功能模块、管理员功能模块、学生功能模块、企业功能模块以及导师功能模块。 前台功能模块&…

微服务知识02

1、九大高并发解决方案 2、系统架构图​​​​​​​ 3、分布式事务 本地事务、分布式事务 操作不同服务器的数据库(垂直分库) 4、分布式事务解决方案(没有seata之前) (1)XA协议(强一致性&a…

PixPin:一键搞定截图、长截图、贴图、GIF

名人说:莫道桑榆晚,为霞尚满天。——刘禹锡(刘梦得,诗豪) 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 目录 一、什么是PixPin?①PixPin②核心功…

readproc.h

Ubuntu22.04系统中 编译自己写的程序的时候,报错,显示找不到readproc.h文件,通过安装libprocps-dev解决 sudo apt install libprocps-dev

项目解决方案:街道社区视频监控接入、汇聚和联网设计方案

目 录 一、客户需求 二、网络拓扑图 三、方案描述 四、系统配置 1、服务器配置 2、带宽配置 五、方案优势 1. 平台可堆叠使用 2. 支持主流接入协议 4. 多种终端显示 5. 客户端功能强大 6. 一机一档 一、客户需求 1,一个街道有十个社…

消息中间件篇之RabbitMQ-高可用机制

一、怎么保证高可用性 在生产环境下,使用集群来保证高可用性,一般我们采用普通集群、镜像集群、仲裁队列。 二、普通集群 普通集群,或者叫标准集群(classic cluster),具备下列特征: 1. 会在集…

蓝桥杯DP算法——区间DP(C++)

根据题意要求的是将石子合并的最小权值,我们可以根据DP思想使用二维数组f[i,j]来存放所有从第i堆石子到第j堆石子合并成一堆石子的合并方式。 然后由第二个图所示,我们可以将i到j区间分成两个区间,因为将i到j合并成一个区间的前一步一定是合…

基于单片机的太阳能电池板自动跟踪系统的研究

摘要 伴随着人类社会的发展,人口基数越来越大,电量消耗巨大,传统发电原 料污染环境的同时,可用量日益减少,给人类未来生产生活带来了一定的威胁, 因而解决日益剧增的用电量,寻求一种新能源显得极其重要。论文正是基于此 背景下,针对当前太阳能电池板采光率低、自动化水…

SpringBoot自动装配的原理

废话不多说,直接上干货 SpringBoot 是如何实现自动装配的? 以我这个2.6.13的版本为例 第一步,我们先从SpringBootApplication这个入口注解看起 在springBootApplication这个注解当中有三个关键性的注解,大概可以看作是&#xff1a…

286.【华为OD机试真题】学生重新排队(JavaPythonC++JS实现)

🚀点击这里可直接跳转到本专栏,可查阅顶置最新的华为OD机试宝典~ 本专栏所有题目均包含优质解题思路,高质量解题代码(Java&Python&C++&JS分别实现),详细代码讲解,助你深入学习,深度掌握! 文章目录 一. 题目-学生重新排队二.解题思路三.题解代码Python题解…

[回溯]组合总和

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。 candidates 中的 同一个 数字可以 无限制重复被选取 。如…

Java面试:Spring Cloud Alibaba

文章目录 引言I Spring Cloud Alibaba1.1 配置文件加载的优先级(由高到低)1.2 注册中心1.3 rpcII 高并发场景:缓存穿透/缓存失效/雪崩如何解决2.1 缓存穿透2.2 缓存击穿(失效)2.3 缓存雪崩引言 微服务涉及的中间件分布式事务事务的传播方式事务的隔离级别缓存穿透/缓存失效…

Matlab/simulink基于vsg的风光储调频系统建模仿真(持续更新)

​ 1.Matlab/simulink基于vsg的风光储调频系统建模仿真(持续更新)

掘根宝典之C语言基本数据类型详解1——整型介绍,整数溢出,大小端,整型常量的存储,整型提升

目录 基本数据类型的介绍 类型的意义 修饰符类型: 整型数据类型 int: short int(通常写short): long int(通常写long): long long(通常写long long): char 使用多种整数类型的原因 整型常量的存储 1.字面常…

C/C++暴力/枚举/穷举题目持续更新(刷蓝桥杯基础题的进!)

目录 前言 一、百钱买百鸡 二、百元兑钞 三、门牌号码(蓝桥杯真题) 四、相乘(蓝桥杯真题) 五、卡片拼数字(蓝桥杯真题) 六、货物摆放(蓝桥杯真题) 七、最短路径(蓝…

图解目标检测 之 【YOLOv9】 算法 最全原理详解

YOLOv9与SOTA模型对比 什么是 YOLOv9?YOLOv9是YOLO系列中的最新产品,是一种实时目标检测模型。它通过先进的深度学习技术和架构设计,包括通用 ELAN (GELAN) 和可编程梯度信息 (PGI),展现出更好的性能。 YOLO 系列通过引入计算机视…

Java/Python/Go不同开发语言基础数据结构和相关操作总结-GC篇

Java/Python/Go不同开发语言基础数据结构和相关操作总结 1. 常见gc方式1.1 gc判断对象是否存活1.2 引用计数法1.2 标记-清除算法1.3 复制算法1.4 标记-压缩算法1.5 分代收集算法 2. java的gc方式以及垃圾回收器2.1 gc方式2.1 gc回收器2.1.1 Serial收集器2.1.2 ParNew收集器2.1.…

防御第六次作业-笔记整理

目录 DPI DFI 签名 AV URL过滤 HTTPS过滤 文件内容过滤 VPN概述 密码学 对称加密 非对称加密 HASH运算 DPI DPI --- 深度包检测技术 --- 主要针对完整的数据包(数据包分片,分段需要重组),之后对数据包的内容进行识别。…

Socket、UDP、TCP协议和简单实现基于UDP的客户端服务端

目录 Socket TCP和UDP区别 UDP:无连接,不可靠传输,面向数据报,全双工 TCP:有连接,可靠传输,面向字节流,全双工 无连接和有连接 可靠传输和不可靠传输 面向数据报和面向字节流…