基于 C++ 11 实现线程池
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ThreadPool.h 11KB

1 year ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. //
  2. // Created by louyu on 2023/6/12.
  3. //
  4. #ifndef THREADPOOL_FINAL_THREADPOOL_H
  5. #define THREADPOOL_FINAL_THREADPOOL_H
  6. #include <vector>
  7. #include <queue>
  8. #include <memory>
  9. #include <atomic>
  10. #include <mutex>
  11. #include <condition_variable>
  12. #include <functional>
  13. #include <unordered_map>
  14. #include <thread>
  15. #include <future>
  16. #include <iostream>
  17. const int TASK_MAX_THRESHOLD = 1024;
  18. const int THREAD_MAX_THRESHOLD = 10;
  19. const int THREAD_MAX_IDLE_TIME = 60; // 单位秒
  20. // 线程池支持的模式
  21. enum class PoolMode {
  22. MODE_FIXED, // 固定数量线程
  23. MODE_CACHED, // 线程数量可动态增长
  24. };
  25. class Thread {
  26. public:
  27. using ThreadFunc = std::function<void(int)>;
  28. Thread(ThreadFunc func): func_(func), threadId_(generateId_ ++) {
  29. }
  30. ~Thread() = default;
  31. void start() { // 启动线程
  32. std::thread t(func_, threadId_); // 创建一个线程执行一个线程函数
  33. t.detach(); // 设置分离线程,否则出作用域后线程对象销毁,线程函数也会中止
  34. }
  35. unsigned getId() const {
  36. return threadId_;
  37. }
  38. private:
  39. ThreadFunc func_;
  40. static unsigned generateId_;
  41. unsigned threadId_; // 线程id,会在线程池对象中建立id与线程对象的映射关系
  42. };
  43. unsigned Thread::generateId_ = 0;
  44. // 线程池类型
  45. class ThreadPool {
  46. public:
  47. ThreadPool()
  48. : initThreadSize_(0)
  49. , taskSize_(0)
  50. , idleThreadSize_(0)
  51. , curThreadSize_(0)
  52. , taskQueMaxThreshold_(TASK_MAX_THRESHOLD)
  53. , threadSizeThreshold_(THREAD_MAX_THRESHOLD)
  54. , poolMode_(PoolMode::MODE_FIXED)
  55. , isPoolRunning_(false) {
  56. }
  57. ~ThreadPool() {
  58. isPoolRunning_ = false;
  59. // 等待线程池中所有的线程返回
  60. std::unique_lock<std::mutex> lock(taskQueMtx_);
  61. notEmpty_.notify_all(); // 线程池对象析构前,唤醒所有被阻塞的线程以返回结果
  62. exitCond_.wait(lock, [&]() {return threads_.empty();});
  63. }
  64. void setMode(PoolMode mode) { // 设置线程池的工作模式
  65. if(checkRunningState()) { // 若线程池已经在运行,禁止修改线程池模式
  66. return;
  67. }
  68. poolMode_ = mode;
  69. }
  70. void setTaskQueMaxThreshold(unsigned threshold) { // 设置task任务队列任务上限
  71. if(checkRunningState()) {
  72. return;
  73. }
  74. if(poolMode_ == PoolMode::MODE_CACHED) {
  75. taskQueMaxThreshold_ = threshold;
  76. }
  77. }
  78. void setThreadSizeThreshold(unsigned threshold) { // 设置线程池cached模式下线程阈值
  79. if(checkRunningState()) {
  80. return;
  81. }
  82. threadSizeThreshold_ = threshold;
  83. }
  84. // 使用可变参模版编程,使得submitTask可以接收任意任务函数与任意数量的参数
  85. // pool.submitTask(sum, 10, 20);
  86. // decltype可以根据括号内的表达式推导类型
  87. template<class Func, class ...Args>
  88. auto submitTask(Func &&func, Args &&...args) -> std::future<decltype(func(args...))> {
  89. // 打包任务,放入任务队列
  90. using RType = decltype(func(args...));
  91. auto task = std::make_shared<std::packaged_task<RType()>>(
  92. std::bind(std::forward<Func>(func), std::forward<Args>(args)...));
  93. std::future<RType> result = task->get_future();
  94. // 获取锁,任务队列不是线程安全的
  95. std::unique_lock<std::mutex> lock(taskQueMtx_); // unique_lock构造同时会获取锁
  96. // 用条件变量等待任务队列有空余,wait一直等待直到后续条件满足 wait_for等待指定时间段 wait_until等待到某时间节点
  97. // 用户提交任务,最长阻塞不能超过1s,否则判断提交任务失败,返回
  98. if(!notFull_.wait_for(lock, std::chrono::seconds(1), [&]() { return taskQue_.size() < taskQueMaxThreshold_; })) {// 不满足lambda表达式条件时wait,释放锁
  99. // 若等待1s条件依然不满足,提交任务失败
  100. std::cerr << "task queue is full, submit task fail." << std::endl;
  101. auto emptyTask = std::make_shared<std::packaged_task<RType()>>( // 任务提交失败,运行一个空任务
  102. []() -> RType {return RType();});
  103. (*emptyTask)(); // 执行空任务,否则主线程调用future.get()会死锁
  104. return emptyTask->get_future();
  105. }
  106. // 若有空余,将任务放入任务队列中
  107. // using Task = std::function<void()>;
  108. // 如何将一个带返回值的线程函数封装到std::function<void()>中,用lambda表达式
  109. taskQue_.emplace([task]() {
  110. (*task)();
  111. });
  112. taskSize_ ++;
  113. // 新放入任务,任务队列必然不空,notEmpty_通知
  114. notEmpty_.notify_all();
  115. // cached模式任务处理较为紧急,适合小且快的任务多场景,需要根据任务数量和空闲线程的数量,决定是否动态扩充线程数
  116. if(poolMode_ == PoolMode::MODE_CACHED
  117. && taskSize_ > idleThreadSize_
  118. && curThreadSize_ < threadSizeThreshold_) {
  119. // 创建新线程
  120. auto ptr = std::make_unique<Thread>(std::bind(&ThreadPool::threadFunc, this, std::placeholders::_1));
  121. unsigned threadId = ptr->getId();
  122. threads_.emplace(threadId, std::move(ptr)); // unique_ptr拷贝构造被删除,要强转右值
  123. threads_[threadId]->start(); // 启动线程
  124. // 修改线程个数相关变量
  125. curThreadSize_ ++;
  126. idleThreadSize_ ++;
  127. }
  128. // 返回Result对象
  129. return result;
  130. }
  131. void start(unsigned initThreadSize = std::thread::hardware_concurrency()) { // 开启线程池,默认开启cpu核心数个线程
  132. isPoolRunning_ = true; // 设置线程池启动状态
  133. initThreadSize_ = initThreadSize; // 初始化初始线程个数
  134. curThreadSize_ = initThreadSize;
  135. // 创建线程对象
  136. for(int i = 0; i < initThreadSize_; i ++) {
  137. auto ptr = std::make_unique<Thread>(std::bind(&ThreadPool::threadFunc, this, std::placeholders::_1));
  138. unsigned threadId = ptr->getId();
  139. threads_.emplace(threadId, std::move(ptr)); // unique_ptr拷贝构造被删除,要强转右值
  140. }
  141. // 启动所有线程
  142. for(int i = 0; i < initThreadSize_; i ++) {
  143. threads_[i]->start();
  144. idleThreadSize_ ++; // 记录初始线程的数量
  145. }
  146. }
  147. ThreadPool(const ThreadPool&) = delete;
  148. ThreadPool& operator=(const ThreadPool&) = delete;
  149. private:
  150. void threadFunc(unsigned threadId) { // 定义线程函数
  151. auto lastTime = std::chrono::high_resolution_clock().now(); // 记录该线程调度时间的变量
  152. for(;;) {
  153. Task task;
  154. {
  155. // 先获取锁
  156. std::unique_lock<std::mutex> lock(taskQueMtx_);
  157. // 当任务队列中有任务的时候,不论线程池是否要析构,先要把任务做完
  158. while(taskQue_.empty()) { // 任务队列为空时
  159. if(!isPoolRunning_) { // 唤醒后如果线程池要析构了,那么停止线程执行
  160. threads_.erase(threadId); // 线程结束前把线程对象从线程列表容器中删除
  161. exitCond_.notify_all(); // 通知线程池的析构函数,有线程析构
  162. return;
  163. }
  164. // 否则,需要等待任务到来
  165. // cached模式下,有可能需要回收之前创建的线程,即超过initThreadSize_数量的线程要进行回收
  166. // 当前时间 - 上一次线程执行时间 > 60s
  167. if(poolMode_ == PoolMode::MODE_CACHED) {
  168. if(std::cv_status::timeout == notEmpty_.wait_for(lock, std::chrono::seconds(1))) { // 条件变量超时返回
  169. auto now = std::chrono::high_resolution_clock().now();
  170. auto dur = std::chrono::duration_cast<std::chrono::seconds>(now - lastTime);
  171. if(dur.count() >= THREAD_MAX_IDLE_TIME && curThreadSize_ > initThreadSize_) {
  172. // 开始回收线程
  173. threads_.erase(threadId); // 把线程对象从线程列表容器中删除
  174. // 记录线程数量的相关变量的值修改
  175. curThreadSize_ --;
  176. idleThreadSize_ --;
  177. return;
  178. }
  179. }
  180. } else { // fixed模式
  181. // 等待notEmpty_条件
  182. notEmpty_.wait(lock);
  183. }
  184. }
  185. idleThreadSize_ --; // 空闲线程--
  186. // 从任务队列取一个任务
  187. task = taskQue_.front();
  188. taskQue_.pop();
  189. taskSize_ --;
  190. // 如果依然有剩余任务,继续通知其它线程执行任务
  191. if(!taskQue_.empty()) {
  192. notEmpty_.notify_all();
  193. }
  194. // 取出任务后必然不满,通知可以继续提交任务
  195. notFull_.notify_all();
  196. } // 释放锁,不能执行任务的时候还占着锁
  197. // 当前线程负责执行这个任务
  198. if(task != nullptr) {
  199. task(); // 执行任务function<void()>
  200. }
  201. idleThreadSize_ ++; // 任务执行完,空闲线程数量增加
  202. lastTime = std::chrono::high_resolution_clock().now(); // 更新该线程被调度执行完的时间
  203. }
  204. }
  205. bool checkRunningState() const {
  206. return isPoolRunning_;
  207. }
  208. private:
  209. std::unordered_map<unsigned, std::unique_ptr<Thread>> threads_; // 线程列表
  210. unsigned initThreadSize_; // 初始线程数量
  211. std::atomic<unsigned int> curThreadSize_; // 记录当前线程池中线程总数量
  212. std::atomic<unsigned int> idleThreadSize_; // 记录空闲线程的数量
  213. unsigned threadSizeThreshold_; // 线程数量上限阈值
  214. // 这里队列里不能存裸指针,避免用户传入一个临时对象,使用智能指针延长外部传进来对象的生命周期
  215. using Task = std::function<void()>;
  216. std::queue<Task> taskQue_; // 任务队列
  217. std::atomic<unsigned int> taskSize_; // 任务队列任务数,用原子变量保证原子性
  218. unsigned taskQueMaxThreshold_; // 任务队列任务数上限
  219. std::mutex taskQueMtx_; // 保证任务队列的线程安全
  220. std::condition_variable notFull_; // 任务队列不满
  221. std::condition_variable notEmpty_; // 任务队列不空
  222. std::condition_variable exitCond_; // 等待线程资源全部回收
  223. PoolMode poolMode_; // 当前线程池的工作模式
  224. std::atomic<bool> isPoolRunning_; // 表示当前线程池的启动状态
  225. };
  226. #endif //THREADPOOL_FINAL_THREADPOOL_H