在上一篇【线程池项目(一)】项目介绍和代码展示 中,我们展示了线程池的两个版本实现,它们的代码在具体的实现细节上是优化过了的。下文提供的代码并非完整,也有很多地方尚需改善,但这些差异对理解整个项目而言,影响并不会太大。因此,在接下来的讨论中,我们将重点放在对项目代码的理解上。
如果需要与本篇博客完全匹配的实现代码,也可以在 我的gitee 上下载对应的源码
项目经历——基于C++新特性以及模板编程实现的线程池
- 一、设计思路
 - 二、关键技术点
 - (1)线程池类ThreadPool
 - (2)线程类Thread
 - (3)信号量类Semaphore
 - (4)上帝类Any
 - (5)任务抽象基类Task
 - (6)提交任务的返回值Result
 
- 三、分析图例,理解函数执行过程
 - 四、总结
 
一、设计思路
使用C++ OOP面向对象编程的思想,设计了6个类:
- ThreadPool: 负责线程池的创建
ThreadPool();、开启start()、设置模式setMode(这里不需要,默认FIXED)、提交任务submitTask()、线程函数threadFunc()(至于为什么要把线程函数放入ThreadPool里,而不是在Thread里,后面会解释) - Thread: 负责线程的创建
Thread(),定义了函数对象类型ThreadFunc - Task: 对外(即用户)提供一个抽象类接口,用户可以重写
run()函数来提供相应任务 - Any: 模仿C++17中的
any,可以接收任意的类型 - Result: 作为
Any的依赖,接收task任务在用户提交完成后的返回值类型 - Semaphore: 线程的通信机制,模仿C++20的
Semaphore,用于通知用户提交的任务是否已经被相应的线程执行完毕 
二、关键技术点
(1)线程池类ThreadPool
🤠开启线程池start():
当创建线程对象时,我们使用std::bind()绑定器将线程池的成员方法threadFunc与当前线程池的this指针绑定到一起,作为创建线程对象的一个参数,传递给std::thread线程对象。这样,在Thread类的成员方法中,就可以直接通过调用ThreadPool的线程函数threadFunc来访问线程池中的成员变量和方法。通过这种绑定方式,也可以解决变参的问题,因为所有相关的参数都已经被绑定到这个线程函数上。因此,我们可以使用别名using ThreadFunc = std::function<void()>来定义线程函数的类型。
🥳提交任务submitTask():
使用互斥锁unique_lock()来维护线程安全,结合条件变量notFull的使用,判断1s内,任务队列中任务的数目是否已达上限,若已达,则阻塞返回提交失败,反之入队,通知其余线程任务队列中有任务能够执行了,返回提交成功任务的返回值Result(sp, true(default)),关于Result见下文
🥸线程函数threadFunc():
由于线程函数需要获取任务队列里的任务,在获取任务的同时需要线程池中互斥锁和条件变量的管理,直接定义在Thread里不好进行相关操作,定义在全局中也不好访问私有的成员变量,所以把它放在ThreadPool里实现,但是我们的目的是让线程通过线程函数来执行任务,所以我们定义相应的Thread构造函数把ThreadPool的线程函数threadFunc给到Thread,用Thread类的成员变量func_来存储。代码同上,就不赘述了
(2)线程类Thread
😎 这里没什么可说的,就是一个分离线程
detach,延长线程函数的生命周期至主线程结束(即创建线程池的线程)
(3)信号量类Semaphore
🤓 模拟C++20的semaphore,用
wait、post分别申请和释放信号量资源来控制用户提交的任务是否被执行完毕
(4)上帝类Any
🧐 考虑到用户提交任务时,可能不仅仅需要把任务处理完成,还可能需要得到任务处理完成后的返回值,所以我们设计了一个
Any类作为接收任意类型的返回值,即让一个类型可以指向其他的任意类型。然我们知道,基类指针可以执行派生类对象,根据data(存储用户返回的数据,用模板实现)构造出一个派生类对象,这时候我们就可以使用Any里的基类指针指向它,并通过向下转型取出data。这里需要理解一下Any的构造函数,编译器在用户提交的任务执行完返回时是如何处理的
(5)任务抽象基类Task
😯 由于
Task和Result是有耦合的,Result里用智能指针shared_ptr定义的Task类型的成员变量task_,为了避免发生智能指针的交叉引用问题,Task里使用了裸指针定义Result类型的result_,指向用户提交任务后返回的Result,其生命周期 > task, 通过result_在Task里面调用Result里的成员方法,来控制Any返回值的获取,在exec()中发生多态调用(run()在基类中为纯虚函数,供用户重写)
(6)提交任务的返回值Result
😳 构造函数Result(…):
判断提交任务是否成功,并把当前对象this给到成员变量task_里,供Task类中的相关方法使用,如上
🥹 获取返回值
Any,setVal()、get():
通过信号量的wait、post操作和资源转移std::move()来进行
三、分析图例,理解函数执行过程

四、总结
以上就是有关于【线程池项目(二)】线程池FIXED模式的实现的内容,下一篇【线程池项目(三)】介绍线程池fixed模式的具体实现过程🔚🔚🔚
🌻🌻🌻如果聪明的你浏览到这篇文章并觉得文章内容对你有帮助,请不吝动动手指,给博主一个小小的赞和收藏🌻🌻🌻














![[算法沉淀记录] 排序算法 —— 冒泡排序](https://img-blog.csdnimg.cn/direct/9b01a18b50a44b08ad45fd9937b9e0fc.png)













