在上一篇【线程池项目(二)】线程池FIXED模式的实现 中我们了解到到线程池fixed
模式的大致实现原理,但对于一个比较完整的项目来说,我们还需要考虑到可能会发生的各种情况,比如用户提交的任务数可能在某一时刻急剧增加,而且每个任务的任务量比较小,在这种情况下,仅仅是在fixed
模式下,可能会造成有些任务永远得不到执行或者等待非常久的时间,使用户体验变差。然,要是我们能够通过改良使能够执行任务的线程动态的创建和删除,那么就能很好的解决这个问题,也就是下面我们要介绍的cached
模式下的线程池
由于第二篇中已经将线程池的关键技术点做了详细的介绍,而
cached
模式也是在fixed
模式的基础上扩展而来的,所以对于下面的代码剖析仅仅关注于cached
模式下的扩展部分
如果需要与本篇博客完全匹配的实现代码,也可以在 我的gitee 上下载对应的源码
项目经历——基于C++新特性以及模板编程实现的线程池
- 一、cached模式设计的实现🧐🧐🧐
- 二、重点理解 ThreadPool::~ThreadPool()析构函数😮😮😮
- 三、思考 :thinking::thinking::thinking:
- 四、总结😩😩😩
一、cached模式设计的实现🧐🧐🧐
这里需要提一句,既然在cached
模式下,我们涉及到线程的动态创建和删除,那么肯定也需要考虑到,当所有任务执行完后,对于空闲线程的资源回收问题,毕竟咱们的CPU资源有限。
🙉 ThreadPool:private
之前fixed
模式下,我们使用的是线程列表vector
(非线程安全),这里需要回收指定线程的话,需要将其换成unordered_map<int, std::unique_ptr<Thread>>
,把每个线程对象都与一个线程ID
对应起来方便管理,而线程ID
的话是由Thread
里的静态变量generateId_
自己生成的,见下图二:
🙈 ThreadPool::start() / ThreadPool::submitTask():
为了使每个线程对象都能够对应一个线程ID,不管是在刚开启线程池创建初始线程数时,还是在任务过多需要动态创建线程时,都需要在使用绑定器时预留一个参数占位符,供线程创建使用、线程回收时删除:
二、重点理解 ThreadPool::~ThreadPool()析构函数😮😮😮
🙊 线程池的析构函数这里很关键,通过设置线程池的运行状态,通知等待线程继续往下执行,用条件变量阻塞等待其他线程,判断其是否都已经结束
建议联系下面给出的线程函数方法的完整代码和测试程序认真理解,分析各种抢锁的情况,以及可能出现的问题,或者直接看我的博客【手写线程池(四)】项目死锁问题的分析
🥹 ThreadPool::threadFunc():
对于线程回收机制,我们规定了两个条件
- 在
fixed
模式下,任务队列为空,无限期阻塞,不回收- 在
cached
模式下,任务队列为空,每次等待1s,轮询至60s,若还是没有任务到来,则回收该线程对于时间,我们使用C++11标准里边的
std::chrono::high_resolution_clock().now()
来获取、std::chrono::duration_cast<std::chrono::seconds>(now - lastTime).count()
来记录时间差
下面来贴一段完整的实现线程函数的代码截图:
这里我再给一段测试程序代码截图
三、思考 🤔🤔🤔
我们知道线程不外乎就两种状态,
- 一种是
wait
状态,等待任务的到来, - 另一种是
exec()
状态,正在执行任务,还未执行完
根据以上三段完整代码,我们是否可以提出几个问题???
- 用于执行任务的线程处于
wait
状态,主线程执行线程池的析构函数,会发生什么 - 用于执行任务的线程处于
exec()
状态,主线程执行线程池的析构函数,会发生什么
其实还有第三种状态!!!
- 当主线程准备执行析构函数把
isPoolRunning
置为false
前,正在执行任务的线程恰好执行完任务并且再一次通过while(isPoolRunning)
进入循环,会发生什么
这三个问题将在下一篇进行剖析,感兴趣的朋友可以先试着分析一下
四、总结😩😩😩
以上就是有关于【线程池项目(三)】线程池CACHED模式的实现 的内容,下一篇【线程池项目(四)】项目死锁问题的分析 将带大家剖析两个经典的死锁问题🔚🔚🔚
🌻🌻🌻如果聪明的你浏览到这篇文章并觉得文章内容对你有帮助,请不吝动动手指,给博主一个小小的赞和收藏🌻🌻🌻