904-线程池项目死锁问题分析

article/2025/9/26 18:06:19

死锁问题

1、在ThreadPool的资源回收,等待线程池所有线程退出时,发生死锁问题,导致进程无法退出
在这里插入图片描述
我们的资源回收代码如下:

//线程池析构
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种情况的处理方法如下:

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

第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://chatgpt.dhexx.cn/article/ReEb0JId.shtml

相关文章

ORACLE 错误 904

错误原因&#xff1a; Oracle 版本中IMP和EXP的兼容问题。 我这里是因为本机上的oracle版本高于服务器上的oracle版本 解决办法&#xff1a; 安装和服务器同版本的10g客户端&#xff0c;在命令窗口化中切换到10g客户端的bin目录下面进行导入就ok了。 为什么要切换呢&#xf…

leetcode:904. 水果成篮

题目来源 leetcode&#xff1a;904. 水果成篮 题目描述 题目解析 题意 题意从任意位置开始&#xff0c;若最多只能收集两种水果&#xff0c;问最多能收集多少个水果。 这道题目可以理解为求只包含两种元素的最长连续子序列&#xff0c;和leetcode&#xff1a;159.最多有两个…

2022-7-8 Leetcode 904.水果成篮

错误的代码&#xff1a; class Solution { public:int totalFruit(vector<int>& fruits) {int start 0;int end 0;set<int> myset;int len 0;for (; end < fruits.size(); end){myset.insert(fruits[end]);if (myset.size() > 2 && start &l…

【LeetCode】图解 904. 水果成篮

904. 水果成篮 904. 水果成篮 你正在探访一家农场&#xff0c;农场从左到右种植了一排果树。这些树用一个整数数组 fruits 表示&#xff0c;其中 fruits[i] 是第 i 棵树上的水果 种类 。 你想要尽可能多地收集水果。然而&#xff0c;农场的主人设定了一些严格的规矩&#xf…

LeetCode_904 水果成篮

1、题目&#xff1a;水果成篮 你正在探访一家农场&#xff0c;农场从左到右种植了一排果树。这些树用一个整数数组 fruits 表示&#xff0c;其中 fruits[i] 是第 i 棵树上的水果 种类 。 你想要尽可能多地收集水果。然而&#xff0c;农场的主人设定了一些严格的规矩&#xff0c…

力扣第904题

一、题目&#xff1a;904. 水果成篮 二、题目解析&#xff1a; 题目解析&#xff1a;题目不太好理解&#xff0c;通俗解释一下&#xff0c;可以把数组中的每个元素理解成一个树&#xff0c;元素值理解成那种类型的果树&#xff0c;比如:[3,3,3,1,2,1,1,2,3,3,4] 我们可以认为1…

LeetCode 904. 水果成篮

904. 水果成篮 题目&#xff1a;你正在探访一家农场&#xff0c;农场从左到右种植了一排果树。这些树用一个整数数组 fruits 表示&#xff0c;其中 fruits[i] 是第 i 棵树上的水果 种类 。 你想要尽可能多地收集水果。然而&#xff0c;农场的主人设定了一些严格的规矩&#xf…

力扣(LeetCode)904. 水果成篮(C语言)

一、环境说明 本文是 LeetCode 904题 : 水果成篮&#xff0c;使用c语言实现滑动窗口哈希集合。测试环境:Visual Studio 2019 二、代码展示 //滑动窗口哈希表&#xff0c;一次遍历O(n) //难点&#xff1a;如何用判断边界的移动时机,应该可以自己实现。 //right和left不一定相…

(解决方案) Visual Studio 2019 连接 SQL Server 2019 数据库时,数据库版本为904无法打开,此服务器支持852版及更低版本的问题

我在做课设程序时连不上数据库&#xff0c;提示数据库版本太高。即使在数据库设置里把兼容性改到2016(852版)仍然无法连接&#xff0c;网上找的解决方法也不成功&#xff0c;自己摸索了一番找到了解决方法。具体是直接连接数据库的服务器&#xff0c;而不是连接数据库本身&…

【Linux】make的工作原理和makefile文件

● makefile文件 make是一个命令&#xff0c;makefile是一个文件。make命令执行时&#xff0c;需要一个 Makefile 文件&#xff0c;以告诉make命令需要怎么样的去编译和链接程序。一个工程中的源文件不计数&#xff0c;其按类型、功能、模块分别放在若干个目录中&#xff0c;ma…

简单的makefile文件编写

习惯了windows下ide创建工程已经代码的编写&#xff0c;然后一键运行&#xff0c;很简单&#xff0c;因为很多事ide都帮我们做了&#xff0c;但是linux下不一样&#xff0c;需要手动编译&#xff0c;执行一条条的命令&#xff0c;一般工程都是由于很多文件组成的&#xff0c;比…

如何编写一个简单的Makefile文件

在c语言学习的初级阶段&#xff0c;我们所写的代码量较少&#xff0c;分装的文件也很少&#xff0c;直接使用gcc编译便能满足我们的大部分需求&#xff0c;然而随着我们学习的深入&#xff0c;代码量越来越多&#xff0c;已经开始做一些工程项目了&#xff0c;项目中包含多个c文…

Linux下编写makefile文件

首先在vi编辑器下编写add.c #include "test.h" #include <stdio.h> int add(int a, int b) { return a b; } int main() { printf(" 2 3 %d\n", add(2, 3)); printf(" 5 - 3 %d\n", sub(5, 3)); return 1; } 再编写sub.c…

如何创建 makefile文件

在学习 任哲《嵌入式实时操作系统 uCOS-II 原理及应用》中如何创建makefile文件呢&#xff1f; 1&#xff0c;创建一个名为makefile的文件&#xff08;取消扩展名&#xff09; 输入下面命令示例内容 target1: md 11 target2: md 22 target3: rd 11 rd 22 2&am…

Makefile文件的简单编写

参考&#xff1a; MakeFile文件是什么——内容、工作原理、作用、使用 嵌入式操作系统linux篇&#xff08;书&#xff09; Makefile伪目标 GNU make中文手册.pdf 在嵌入式开发中&#xff0c;一个工程中的源文件是非常多的&#xff0c;如果一个个编译会很麻烦&#xff0c;Makefi…

带你去了解什么是makefile文件

GNU make命令是用来控制从源文件生成可执行文件或非可执行文件的方式。那么make命令又是通过makefile文件来控制了。所以了解makefile文件就显得很有必要了。 makefile文件由许多规则组成&#xff0c;这些规则的形式一般是这样的&#xff1a; 目标 ... : 先决条件 ...命令目标…

linux下制作makefile文件

1.最简单的一种当然也是最麻烦的一种(makefile 中的代码如下&#xff0c;其中hello.cpp是文件名&#xff0c;hello是编译后的命名。使用方法是直接输入 make) hello:hello.cpp …

编写 Makefile文件 (一)

参考&#xff1a;《linux程序设计&#xff08;第四版&#xff09;》 本文的编写从简单到复杂&#xff0c;一步一步完成Makefile文件的编写和完善。首先看一下我们的程序有哪些文件&#xff1a; 文件内的程序也很简单&#xff0c;就是输出该文件已经被调用&#xff0c;代码如下…

如何编写makefile文件

文章目录 1. makefile文件的作用2. makefile文件编写格式C代码举例编写makefile文件 3. makefile编写习惯优化使用中间文件定义变量自动变量通配符假想目标 1. makefile文件的作用 在很多的生产环境中&#xff0c;一个项目的运行往往要有很多源程序文件生成最终的可执行文件&am…

Linux下makefile文件的编写

在学习如何编写makefile文件之前&#xff0c;我们首先需要了解什么是makefile文件&#xff1a; makefile描述了整个工程的编译和链接等规则。它指明了哪些文件需要先编译&#xff0c;哪写文件需要后编译&#xff0c;哪些文件需要重新编译&#xff0c;甚至进行更复杂的功能操作。…