在上一篇【线程池项目(一)】项目介绍和代码展示 中,我们展示了线程池的两个版本实现,它们的代码在具体的实现细节上是优化过了的。下文提供的代码并非完整,也有很多地方尚需改善,但这些差异对理解整个项目而言,影响并不会太大。因此,在接下来的讨论中,我们将重点放在对项目代码的理解上。
如果需要与本篇博客完全匹配的实现代码,也可以在 我的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
模式的具体实现过程🔚🔚🔚
🌻🌻🌻如果聪明的你浏览到这篇文章并觉得文章内容对你有帮助,请不吝动动手指,给博主一个小小的赞和收藏🌻🌻🌻