死锁问题分析和解决——资源回收时

1.描述问题

在完成线程池核心功能功能时,没有遇到太大的问题(Any,Result,Semfore的设计),在做线程池资源回收时,遇到了死锁的问题        

1、在ThreadPool的资源回收,等待线程池所有线程退出时,发生死锁问题,导致进程无法退出

死锁代码:

#include "threadpool.h"#include <thread>
#include <iostream>const int TASK_MAX_THRESHHOLD = INT32_MAX;
const int THREAD_MAX_THRESHHOLD = 100;
const int THREAD_MAX_IDLE_TIME = 60;//单位:秒//线程池构造
ThreadPool::ThreadPool(): initThreadSize_(0), taskSize_(0), idleThreadSize_(0)//刚开始时还没有线程, curThreadSize_(0), taskQueMaxThreshHold_(TASK_MAX_THRESHHOLD), threadSizeThreshHold_(THREAD_MAX_THRESHHOLD), poolMode_(PoolMode::MODE_FIXED), isPoolRunning_(false)
{}//线程池析构
ThreadPool::~ThreadPool()
{isPoolRunning_ = false;notEmpty_.notify_all();//等待线程池里面所有的线程返回  有两种状态:阻塞 & 正在执行任务中std::unique_lock<std::mutex> lock(taskQueMtx_);exitCond_.wait(lock, [&]()->bool {return threads_.size() == 0; });
}//设置线程池的工作模式
void ThreadPool::setMode(PoolMode mode)
{if (checkRunningState())return;poolMode_ = mode;
}// 设置task任务队列上限阈值
void ThreadPool::setTaskQueMaxThreshHold(int threshhold)
{if (checkRunningState())return;taskQueMaxThreshHold_ = threshhold;
}//设置线程池cached模式下线程阈值
void ThreadPool::setThreadSizeThreshHold(int threshhold)
{if (checkRunningState())return;if (poolMode_ == PoolMode::MODE_CACHED){threadSizeThreshHold_ = threshhold;}
}// 给线程池提交任务  用户调用该接口,传入任务对象,生产任务
Result ThreadPool::submitTask(std::shared_ptr<Task> sp)
{//获取锁std::unique_lock<std::mutex> lock(taskQueMtx_);//线程的通信  等待任务队列有空余// 用户提交任务,最长不能阻塞超过1s,否则判断提交任务失败,返回//while (taskQue_.size() == taskQueMaxThreshHold_)//{//	notFull_.wait(lock);//}/** wait:直到等待满足条件(第二个参数lamada)才返回* wait_for:满足条件返回真,到了约定的时间段(5s)返回假* wait_until:满足条件返回真,到了约定的时间点(下周一)返回假*/if (!notFull_.wait_for(lock, std::chrono::seconds(1),[&]()->bool {return taskQue_.size() < (size_t)taskQueMaxThreshHold_; }))//等同于上面的语句,参数:需要释放的锁  函数对象(要能满足条件变量)//任务队列中的任务数小于上限的阈值,否则就阻塞在这句{//表示notFull_等待1s,条件依然没有满足std::cerr << "task queue is full,submit task fail." << std::endl;//return task->getResult(); //Task  Result  线程执行完task,task对象就被析构掉了return Result(sp, false);//返回临时对象,应该自动匹配右值的资源转移,如果编译不通过,把C++标准调高一点}//如果有空余,把任务放入任务队列中taskQue_.emplace(sp);taskSize_++;//因为新放了任务,任务队列肯定不空了,在notEmpty_上进行通知,赶快分配线程执行任务notEmpty_.notify_all();//cached模式 任务处理比较紧急 场景:小而快的任务 需要根据任务数量和空闲线程的数量,判断是否需要创建新的线程出来if (poolMode_ == PoolMode::MODE_CACHED&& taskSize_ > idleThreadSize_&& curThreadSize_ < threadSizeThreshHold_){std::cout << ">>> create new thread..." << std::this_thread::get_id() << " exit!" << std::endl;//创建新的线程对象auto ptr = std::make_unique<Thread>(std::bind(&ThreadPool::threadFunc, this, std::placeholders::_1));int threadId = ptr->getId();threads_.emplace(threadId, std::move(ptr));//threads_.emplace_back(std::move(ptr));//启动线程threads_[threadId]->start();//修改线程个数相关的变量curThreadSize_++;idleThreadSize_++;}//返回任务的Result对象return Result(sp);// return task->getResult();
}//开启线程池
void ThreadPool::start(int initThreadSize)
{//设置线程池的运行状态isPoolRunning_=true;//记录初始线程个数initThreadSize_ = initThreadSize;curThreadSize_ = initThreadSize;//创建线程对象for (int i = 0; i < initThreadSize_; i++){//创建thread线程对象的时候,把线程函数给到thread线程对象auto ptr = std::make_unique<Thread>(std::bind(&ThreadPool::threadFunc, this, std::placeholders::_1));int threadId = ptr->getId();threads_.emplace(threadId, std::move(ptr));//threads_.emplace_back(std::move(ptr));//unique_ptr将左值引用的拷贝构造和赋值都delete了,需要右值(进行资源转移)}//启动所有线程 std::vector<Thread*> threads_;for (int i = 0; i < initThreadSize_; i++){threads_[i]->start(); //需要去执行一个线程函数idleThreadSize_++;//记录初始空闲线程的数量}
}//定义线程函数  线程池的所有线程从任务队列里面消费任务
void ThreadPool::threadFunc(int threadid) //线程函数返回,相应的线程也就结束了
{/*std::cout << "begin threadFunc tid:" << std::this_thread::get_id() << std::endl;std::cout << "end threadFunc tid:" << std::this_thread::get_id() << std::endl;*/auto lastTime = std::chrono::high_resolution_clock().now();while (isPoolRunning_){std::shared_ptr<Task> task;{//先获取锁std::unique_lock<std::mutex> lock(taskQueMtx_);std::cout << "tid:" << std::this_thread::get_id()<< "尝试获取任务..." << std::endl;//cached模式下,有可能已经创建了很多的线程,但是空闲时间超过60s,应该把多余的线程结束回收掉(超过initThreadSize_数量的线程要进行回收)//当前时间-上一次线程执行的时间>60s//每一秒中返回一次  怎么区分:超时返回?还是有任务待执行返回while (taskQue_.size() == 0){if (poolMode_ == PoolMode::MODE_CACHED){//条件变量,超时返回了if (std::cv_status::timeout == notEmpty_.wait_for(lock, std::chrono::seconds(1))){auto now = std::chrono::high_resolution_clock().now();auto dur = std::chrono::duration_cast<std::chrono::seconds>(now - lastTime);if (dur.count() >= THREAD_MAX_IDLE_TIME && curThreadSize_ > initThreadSize_){//开始回收当前线程//记录线程数量的相关变量的值修改//把线程对象从线程列表容器中删除  没有办法  threadFunc  <=>thread对象//threadid=>thread对象=》删除threads_.erase(threadid);// 这个id不是std::this_thread::getid()  是自己生成的,我们自定义的curThreadSize_--;idleThreadSize_--;std::cout << "threadid:" << std::this_thread::get_id() << "exit!" << std::endl;return;}}}else{//等待notEmpty条件notEmpty_.wait(lock);}//线程池结束,回收线程资源if (!isPoolRunning_){threads_.erase(threadid);// 这个id不是std::this_thread::getid()  是自己生成的,我们自定义的std::cout << "threadid:" << std::this_thread::get_id() << "exit!" << std::endl;exitCond_.notify_all();return;}}idleThreadSize_--;//唤醒线程工作,空闲线程-1std::cout << "tid:" << std::this_thread::get_id()<< "获取任务成功..." << std::endl;//从任务队列中取一个任务出来task = taskQue_.front();taskQue_.pop();taskSize_--;//如果依然有剩余任务,继续通知其它的线程执任务if (taskQue_.size() > 0){notEmpty_.notify_all();}//取出一个任务,进行通知,通知可以继续提交生产任务notFull_.notify_all();}//就应该把锁释放掉//当前线程负责执行这个任务if (task != nullptr){//task->run();//执行任务;把任务的返回值setVal方法给到Resulttask->exec();}idleThreadSize_++;//线程执行完任务,空闲线程+1lastTime = std::chrono::high_resolution_clock().now();//更新线程执行完任务的时间}threads_.erase(threadid);// 这个id不是std::this_thread::getid()  是自己生成的,我们自定义的std::cout << "threadid:" << std::this_thread::get_id() << "exit!" << std::endl;exitCond_.notify_all();
}bool ThreadPool::checkRunningState() const
{return isPoolRunning_;
}///   线程方法实现
int Thread::generateId_ = 0;//线程构造
Thread::Thread(ThreadFunc func):func_(func),threadId_(generateId_++)
{}//线程析构
Thread::~Thread(){}//启动线程
void Thread::start()
{//创建一个线程来执行一个线程函数std::thread t(func_, threadId_);//C++11来说 线程对象t  和线程函数func_t.detach();//设置分离线程,线程对象t出作用域会析构,但是线程函数不能结束否则程序会挂掉,所以要将线程分离出去,做到二者互不影响//pthread_detach  pthread_t设置成分离线程//主线程要用pthread_join回收线程,防止孤儿线程的出现}	//获取线程id
int Thread::getId()const
{return threadId_;
}///   Task方法实现
Task::Task():result_(nullptr)
{}void Task::exec()
{result_->setVal(run());//这里发生多态调用
}void Task::setResult(Result* res)
{result_ = res;
}///   Result方法的实现
Result::Result(std::shared_ptr<Task> task, bool isValid):isValid_(isValid),task_(task)
{task_->setResult(this);
}Any Result::get() // 用户调用的
{if (!isValid_){return "";}//task任务如果没有执行完,这里会阻塞用户的线程sem_.wait();//用户调用get时,如果任务在线程池中,还没有被执行完,那么调用get方法的线程就会阻塞住return std::move(any_);//右值引用
}void Result::setVal(Any any)//谁调用的呢??
{//存储task的返回值this->any_ = std::move(any);sem_.post();//已经获取的任务的返回值,增加信号量资源
}

 

我们的资源回收代码如下:

//线程池析构
ThreadPool::~ThreadPool()
{isPoolRunning_ = false;notEmpty_.notify_all();//等待线程池里面所有的线程返回  有两种状态:阻塞 & 正在执行任务中std::unique_lock<std::mutex> lock(taskQueMtx_);exitCond_.wait(lock, [&]()->bool {return threads_.size() == 0; });
}

现在,有的线程没有被回收,线程队列中还有线程,所以就一直阻塞等待了。
线程池的那个线程为什么没有被回收掉?
(时而出现,时而不出现的问题)

我们通过在windows上调试:

我们通过在Linux上进行gdb调试

主要通过gdb attach到正在运行的进程,通过info threads,thread tid,bt等命令查看各个线程的调用堆栈信息,结合项目代码,定位到发生死锁的代码片段,分析死锁问题发生的原因

2.分析问题

原先针对上面的2种情况的处理方法如下:

第3种情况:
有的线程执行完任务,又进入while循环了

在这里有2种情况:
1、pool线程先获取到锁,线程池的线程获取不到锁,阻塞。
此时pool线程看wait条件,size>0,不满足条件,就进入等待wait状态了,并且把互斥锁mutex释放掉。
线程池的线程就获取到锁了,发现任务队列没有任务了,这个任务就在notEmpty条件变量上wait,但是此时pool线程没有办法再对这个条件变量notify了。
发生死锁了!!!

2、线程池里的线程先获取到锁,发生任务队列为空,在条件变量notEmpty上wait了,释放锁,然后pool线程抢到锁,只是看exitCond条件变量的wait条件,看size还是大于0,还是死锁了。

解决方法:pool线程获取到锁后再notify

//线程池析构
ThreadPool::~ThreadPool()
{isPoolRunning_ = false;//等待线程池里面所有的线程返回  有两种状态:阻塞 & 正在执行任务中std::unique_lock<std::mutex> lock(taskQueMtx_);notEmpty_.notify_all();exitCond_.wait(lock, [&]()->bool {return threads_.size() == 0; });
}

 我们在消费者线程进行锁+双重判断:

//定义线程函数   线程池的所有线程从任务队列里面消费任务
void ThreadPool::threadFunc(int threadid)//线程函数返回,相应的线程也就结束了
{auto lastTime = std::chrono::high_resolution_clock().now();//所有任务必须执行完成,线程池才可以回收所有线程资源for (;;){std::shared_ptr<Task> task;{//先获取锁,我们要注意控制锁的范围,取完任务,就释放锁std::unique_lock<std::mutex> lock(taskQueMtx_);std::cout << "tid:" << std::this_thread::get_id()<< "尝试获取任务..." << std::endl;//cached模式下,有可能已经创建了很多的线程,但是空闲时间超过60s,应该把多余的线程//结束回收掉(超过initThreadSize_数量的线程要进行回收)//当前时间 - 上一次线程执行的时间 > 60s//每一秒中返回一次   怎么区分:超时返回?还是有任务待执行返回//锁 + 双重判断while (taskQue_.size() == 0){//线程池要结束,回收线程资源if (!isPoolRunning_){threads_.erase(threadid);//std::this_thread::getid()std::cout << "threadid:" << std::this_thread::get_id() << " exit!"<< std::endl;exitCond_.notify_all();return;//线程函数结束,线程结束}if (poolMode_ == PoolMode::MODE_CACHED){//条件变量,超时返回了if (std::cv_status::timeout ==notEmpty_.wait_for(lock, std::chrono::seconds(1))){auto now = std::chrono::high_resolution_clock().now();auto dur = std::chrono::duration_cast<std::chrono::seconds>(now - lastTime);if (dur.count() >= THREAD_MAX_IDLE_TIME&& curThreadSize_ > initThreadSize_)//任务数量大于空闲线程数量{//开始回收当前线程//记录线程数量的相关变量的值修改//把线程对象从线程列表容器中删除   没有办法 threadFunc《=》thread对象//通过threadid => thread对象 => 删除threads_.erase(threadid);//std::this_thread::getid()curThreadSize_--;idleThreadSize_--;std::cout << "threadid:" << std::this_thread::get_id() << " exit!"<< std::endl;return;}}}else{//等待notEmpty条件notEmpty_.wait(lock);}//if (!isPoolRunning_)//{//	threads_.erase(threadid);//std::this_thread::getid()//	std::cout << "threadid:" << std::this_thread::get_id() << " exit!"//		<< std::endl;//	exitCond_.notify_all();//	return;//结束线程函数,就是结束当前线程了!//}}idleThreadSize_--;std::cout << "tid:" << std::this_thread::get_id()<< "获取任务成功..." << std::endl;//从任务队列种取一个任务出来task = taskQue_.front();taskQue_.pop();taskSize_--;//如果依然有剩余任务,继续通知其它得线程执行任务if (taskQue_.size() > 0){notEmpty_.notify_all();}//取出一个任务,进行通知,通知可以继续提交生产任务notFull_.notify_all();} //就应该把锁释放掉//当前线程负责执行这个任务if (task != nullptr){//task->run();//执行任务;把任务的返回值setVal方法给到Result,基类指针调用派生类对象的同名覆盖方法task->exec();//用户还是使用run方法}idleThreadSize_++;lastTime = std::chrono::high_resolution_clock().now();//更新线程执行完任务的时间}
}

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

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

相关文章

Window下node安装以及配置

在 Windows 下安装 Node.js 非常简单&#xff0c;你可以通过官方提供的安装程序或者使用多版本管理工具&#xff08;如 NVM-Win&#xff09;来进行安装。下面是两种方法的具体步骤&#xff1a; 1. 安装 Node.js程序 步骤如下&#xff1a; 访问官方网站&#xff1a; 访问 Node…

【Redis】数据类型详解及其应用场景

目录 Redis 常⻅数据类型预备知识基本全局命令小结 数据结构和内部编码单线程架构引出单线程模型为什么单线程还能这么快 Redis 常⻅数据类型 Redis 提供了 5 种数据结构&#xff0c;理解每种数据结构的特点对于 Redis 开发运维⾮常重要&#xff0c;同时掌握每种数据结构的常⻅…

【Oracle篇】统计信息和动态采样的深度剖析(第一篇,总共六篇)

&#x1f4ab;《博主介绍》&#xff1a;✨又是一天没白过&#xff0c;我是奈斯&#xff0c;DBA一名✨ &#x1f4ab;《擅长领域》&#xff1a;✌️擅长Oracle、MySQL、SQLserver、阿里云AnalyticDB for MySQL(分布式数据仓库)、Linux&#xff0c;也在扩展大数据方向的知识面✌️…

Go 语言运算符

Go 语言运算符 Go 语言是一种静态类型、编译型语言&#xff0c;由 Google 开发&#xff0c;旨在提高多核处理器下的编程效率。Go 语言在设计上注重简洁和高效&#xff0c;其运算符就是这一设计理念的体现。本文将详细介绍 Go 语言中的运算符&#xff0c;包括算术运算符、比较运…

PostgreSQL-02-入门篇-查询数据

文章目录 1 简单查询SELECT 语句简介SELECT 语句语法SELECT 示例1) 使用 SELECT 语句查询一列数据的示例2) 使用 SELECT 语句查询多列数据的示例3) 使用 SELECT 语句查询表所有列数据的示例4) 使用带有表达式的 SELECT 语句的示例5) 使用带有表达式的 SELECT 语句的示例 2 列别…

地平线—征程2(Journey 2-J2)芯片详解(10)—中断映射

写在前面 本系列文章主要讲解地平线征程2&#xff08;Journey 2-J2&#xff09;芯片的相关知识&#xff0c;希望能帮助更多的同学认识和了解征程2&#xff08;Journey 2-J2&#xff09;芯片。 若有相关问题&#xff0c;欢迎评论沟通&#xff0c;共同进步。(*^▽^*) 错过其他章…

MySQL基础:函数

&#x1f48e;所属专栏&#xff1a;MySQL 函数是指一段可以直接被另一段程序调用的程序或代码&#xff0c;在MySQL中也内置了许多函数供开发者去调用&#xff0c;例如之前提到的聚合函数&#xff0c;本节再去介绍一些其他常用的函数 字符串函数 函数功能CONCAT(S1,S2...Sn)字…

可视化编程-七巧低代码入门02

1.1.什么是可视化编程 非可视化编程是一种直接在集成开发环境中&#xff08;IDE&#xff09;编写代码的编程方式&#xff0c;这种编程方式要求开发人员具备深入的编程知识&#xff0c;开发效率相对较低&#xff0c;代码维护难度较大&#xff0c;容易出现错误&#xff0c;也需要…

《图解设计模式》笔记(三)生成实例

五、Singleton模式&#xff1a;只有一个实例 Singleton 是指只含有一个元素的集合。因为本模式只能生成一个实例&#xff0c;因此以 Singleton命名。 示例程序类图 Singleton.java public class Singleton {private static Singleton singleton new Singleton();private Si…

[Meachines] [Easy] bounty web.config 文件上传代码注入+内核MS10-092权限提升

信息收集 IP AddressOpening Ports10.10.10.93TCP:80 $ nmap -p- 10.10.10.93 --min-rate 1000 -sC -sV PORT STATE SERVICE VERSION │ 80/tcp open http Microsoft IIS httpd 7.5 …

x264是怎样实现多种位深(bitdepth)支持的?

随着生活水平的提高&#xff0c;小伙伴们的眼神也越发好了。8bit 位深不够用了&#xff0c;现在都 10bit&#xff0c;12bit 了。鼠哥编 x264 的时候&#xff0c;也都必须是支持 8bit 和 10bit 位深的。x264 在不同位深的支持上&#xff0c;颇具奇技淫巧。其实 x265 也不遑多让&…

使用Element UI组件时,icon图标不显示

问题描述&#xff1a; 我在使用Element UI组件的日期选择器时&#xff0c;发现图标不显示(左边是原图&#xff0c;右边的问题图)。 经过检查我发现&#xff0c;我的JS&#xff0c;CSS文件都没有问题&#xff0c;只是缺少了element-icons.tff和element-icons.woff这两个文件。 …

【运维项目经历|041】上云项目-物理机迁移到阿里云

🍁博主简介: 🏅云计算领域优质创作者 🏅2022年CSDN新星计划python赛道第一名 🏅2022年CSDN原力计划优质作者 ​ 🏅阿里云ACE认证高级工程师 ​ 🏅阿里云开发者社区专家博主 💊交流社区:CSDN云计算交流社区欢迎您的加入! 目录 项目名称 项目背景 项目目标 项…

微信小程序的四种弹窗使用

​ 在做小程序的过程中&#xff0c;弹窗也算是非常实用的功能了&#xff0c;这几天写的几个功能就用到了弹窗&#xff0c;也可能是初学者的问题&#xff0c;比较菜&#xff0c;想找一个可以带图片的自定义的弹窗&#xff0c;&#xff0c;这里简单介绍一下官方封装好的四个弹窗…

密码学之RSA算法

文章目录 1. RSA算法介绍1.2 算法历史与发展1.3 算法应用场景 2. RSA密钥生成2.1 选择素数2.2 计算公钥和私钥2.3 密钥长度与安全性 3 算法原理3.1 加密原理3.2 加密方法3.3 加密示例3.4 代码实现 4. 总结 1. RSA算法介绍 1.2 算法历史与发展 RSA算法由Ron Rivest、Adi Shami…

Qt 0814作业

一、思维导图 二、登录窗口界面 自由发挥登录窗口的应用场景&#xff0c;实现一个登录窗口界面 要求&#xff1a;每行代码都有注释 【需要用到的图片或者动图&#xff0c;自己去网上找】 #include "mywidget.h"MyWidget::MyWidget(QWidget *parent): QWidget(par…

Java-文件下载(后端response直接下载文件还是后端将文件流返回给前端,由前端负责下载)

Java 后端处理文件下载时,有两种常见的方法:一种是后端直接下载文件,另一种是后端将文件 流返回给前端,由前端负责下载。这两种方法各有优缺点,适用于不同的场景。 1. 后端直接下载 在这种情况下,后端会直接处理文件的下载逻辑,通常涉及到设置HTTP响应头,将文件内容…

【OCR 学习笔记】二值化——全局阈值方法

二值化——全局阈值方法 固定阈值方法Otsu算法在OpenCV中的实现固定阈值Otsu算法 图像二值化&#xff08;Image Binarization&#xff09;是指将像素点的灰度值设为0或255&#xff0c;使图像呈现明显的黑白效果。二值化一方面减少了数据维度&#xff0c;另一方面通过排除原图中…

微服务架构的介绍

系统架构的演变 随着互联⽹的发展&#xff0c;⽹站应⽤的规模不断扩⼤&#xff0c;常规的应⽤架构已⽆法应对&#xff0c;分布式服务架构以及微服务架构势在必⾏&#xff0c;必需⼀个治理系统确保架构有条不紊的演进。 单体应用架构 Web应⽤程序发展的早期&#xff0c;⼤部分…

C++入门——“继承”

一、引入 面相对象的计算机语言有三大特性&#xff1a;“封装”、“继承”、“多态”。今天来讲解一下C的一大重要特性——继承。 通俗理解来讲&#xff0c;继承就和现实生活一样&#xff0c;子辈继承父辈的一些特性&#xff0c;C中的继承也可以这样理解。它允许我们在保持原有…