飞道的博客

【Linux学习】高并发服务器框架 线程池介绍+线程池封装

559人阅读  评论(0)

目录

前言

一、线程池介绍

💻线程池基本概念

💻线程池组成部分

💻线程池工作原理 

二、线程池代码封装

🌈main.cpp

🌈ThreadPool.h

🌈ThreadPool.cpp

🌈ChildTask.h 

🌈ChildTask.cpp

🌈BaseTask.h

🌈BaseTask.cpp

三、测试效果

四、总结

📌创建线程池的好处


前言

本文主要学习Linux内核编程,结合Visual Studio 2019进行跨平台编程,内容包括线程池介绍以及线程池封装

一、线程池介绍

💻线程池基本概念

  • 线程池是预先创建线程的一种技术 (服务器真正意义上实现高并发就必须用线程池)
  • 🌰举个例子:生活中的水池,是装东西的容器,用来装水的,线程池当然就是拿来装线程的
  • 线程池在任务还没有到来之前,创建一定数量的线程,放入空闲队列中,这些线程都是处于阻塞状态,不消耗CPU,但占用较小的内存空间
  • 当新任务到来时,缓冲池选择一个空闲线程,把任务传入此线程中运行,如果缓冲池已经没有空闲线程,则新建若干个线程,当系统比较空闲时,大部分线程都一直处于暂停状态,线程池自动销毁一部分线程,回收系统资源

💻线程池组成部分

  • 线程池类

        维护工作者线程队列(包括空闲与忙碌队列)

        维护一个任务队列

        维护一个线程池调度器指针

  • 线程池调度器(本身也是一个线程)

        负责线程调度

        负责任务分配

  • 工作者线程类(线程池中的线程类的封装)
  • 任务队列
  • 任务接口(实际的业务逻辑都继承自该接口)

💻线程池工作原理 

根据服务器的需要,来设置线程的数量,可能是10条、20条、30条,根据服务器的承载,10条不代表只能做10个任务,总有任务做的快,有的做的慢,可能可以完成20个任务 

🌰举个例子:如上动图所示,当我们服务器收到一个注册业务,是一个服务器要执行的任务,它会进入到任务队列,队列先进先出,顺次执行,任务会唤醒空闲列表当中的一个空闲的线程,接到任务之后,空闲线程会从空闲列表中消失,进入到忙碌列表,去完成对应的任务,完成任务后,从忙碌列表中出去,到空闲列表继续等待新任务

如果,所有的线程都在忙,都在做任务,这时候登录进来,先进入任务队列,会创建一个新的线程来接这个任务,当所有线程都完成任务,回到空闲列表后,新创建的线程销毁,留下原先设置的对应数量线程(类似,保留老员工,把实习生裁员)

  • 队列:先进先出
  • 空闲列表(链表):不定长(有的时候可能需要创建新线程来接任务)
  • 忙碌列表(链表):不定长(有的时候可能需要创建新线程来接任务)

话不多说,咱们上号,封装一下线程池相关函数,来进行测试 

 

二、线程池代码封装

🌈main.cpp

  • 主函数,设置10条线程,来执行30个任务 

  
  1. #include <iostream>
  2. #include <stdio.h>
  3. #include "ThreadPool.h"
  4. #include "ChildTask.h"
  5. using namespace std;
  6. int main()
  7. {
  8. ThreadPool* pool = new ThreadPool( 10); //10条线程
  9. for ( int i = 0; i < 30; i++) //设置30个任务
  10. {
  11. char buf[ 40] = { 0 }; //初始化
  12. sprintf(buf, "%s%d", "任务", i);
  13. BaseTask* task = new ChildTask(buf);
  14. pool-> pushTask(task);
  15. }
  16. while ( 1) {}
  17. return 0;
  18. }

🌈ThreadPool.h

  • 对线程池进行设计,核心包括最大、最小线程数忙碌列表空闲列表任务队列互斥量条件变量,以及线程执行函数

  
  1. #pragma once
  2. #include <queue>//队列
  3. #include <list>//链表头文件
  4. #include <pthread.h>//线程头文件
  5. #include <algorithm>//find查找
  6. #include <iostream>
  7. #include "BaseTask.h"
  8. using namespace std;
  9. #define MIN_NUM 10//最小值 默认参数
  10. class ThreadPool
  11. {
  12. public:
  13. ThreadPool( const int num = MIN_NUM);
  14. ~ ThreadPool();
  15. //判断任务队列是否为空
  16. bool QueueIsEmpty();
  17. //线程互斥量加锁解锁
  18. void Lock();
  19. void Unlock();
  20. //线程条件变量等待和唤醒
  21. void Wait();
  22. void WakeUp();
  23. //添加任务到任务队列
  24. void pushTask(BaseTask* task);
  25. //从任务队列移除任务
  26. BaseTask* popTask(BaseTask* task);
  27. //从忙碌回到空闲 工作结束
  28. void MoveToIdle(pthread_t id);
  29. //从空闲到忙碌 工作开始
  30. void MoveToBusy(pthread_t id);
  31. //线程执行函数
  32. static void* RunTime(void* vo);
  33. private:
  34. int threadMinNum; //最大线程数量
  35. int threadMaxNum; //最小线程数量
  36. queue<BaseTask*>taskQueue; //任务队列
  37. list< pthread_t>busyList; //线程忙碌列表
  38. list< pthread_t>idleList; //线程空闲列表
  39. pthread_mutex_t mutex; //互斥量:做锁
  40. pthread_cond_t cond; //条件变量:让线程等待或者唤醒
  41. };

🌈ThreadPool.cpp

  • 对函数进行参数的设置,核心在于线程执行函数上的设置,在工作前和工作完设置打印,方便我们进行观察

  
  1. #include "ThreadPool.h"
  2. ThreadPool:: ThreadPool( const int num)
  3. {
  4. this->threadMinNum = num;
  5. //条件变量、互斥量初始化
  6. pthread_mutex_init(& this->mutex, NULL);
  7. pthread_cond_init(& this->cond, NULL);
  8. pthread_t id;
  9. //线程num条创建
  10. for ( int i = 0; i < this->threadMinNum; i++)
  11. {
  12. //线程创建
  13. pthread_create(&id, NULL, RunTime, this);
  14. this->idleList. push_back(id); //线程存入空闲列表
  15. }
  16. }
  17. ThreadPool::~ ThreadPool()
  18. {
  19. }
  20. //任务队列是否为空
  21. bool ThreadPool::QueueIsEmpty()
  22. {
  23. return this->taskQueue. empty();
  24. }
  25. //线程加锁
  26. void ThreadPool::Lock()
  27. {
  28. pthread_mutex_lock(& this->mutex);
  29. }
  30. //线程解锁
  31. void ThreadPool::Unlock()
  32. {
  33. pthread_mutex_unlock(& this->mutex);
  34. }
  35. //线程等待
  36. void ThreadPool::Wait()
  37. {
  38. pthread_cond_wait(& this->cond, & this->mutex);
  39. }
  40. //线程唤醒
  41. void ThreadPool::WakeUp()
  42. {
  43. pthread_cond_signal(& this->cond);
  44. }
  45. //添加任务到任务队列
  46. void ThreadPool::pushTask(BaseTask* task)
  47. {
  48. Lock();
  49. taskQueue. push(task);
  50. Unlock();
  51. WakeUp();
  52. }
  53. //从任务队列移除任务
  54. BaseTask* ThreadPool::popTask(BaseTask* task)
  55. {
  56. task = this->taskQueue. front(); //从队列头取
  57. this->taskQueue. pop(); //删除队列头
  58. return task;
  59. }
  60. //从忙碌回到空闲 工作结束
  61. void ThreadPool::MoveToIdle(pthread_t id)
  62. {
  63. list< pthread_t>::iterator iter;
  64. iter = find(busyList. begin(), busyList. end(), id);
  65. if (iter != busyList. end())
  66. {
  67. //从忙碌移除
  68. this->busyList. erase(iter);
  69. //添加到空闲
  70. this->idleList. push_back(*iter); //this->idleList.push_back(id)
  71. }
  72. }
  73. //从空闲到忙碌 工作开始
  74. void ThreadPool::MoveToBusy(pthread_t id)
  75. {
  76. list< pthread_t>::iterator iter;
  77. iter = find(idleList. begin(), idleList. end(), id);
  78. if (iter != idleList. end())
  79. {
  80. //从空闲移除
  81. this->idleList. erase(iter);
  82. //添加到忙碌
  83. this->busyList. push_back(*iter); //this->idleList.push_back(id)
  84. }
  85. }
  86. //线程执行函数
  87. void* ThreadPool::RunTime(void* vo)
  88. {
  89. //拿到执行线程自己的id 因为后面要处理忙碌和空闲的情况
  90. pthread_t id = pthread_self();
  91. //确保主线程与子线程分离,子线程结束后,资源自动回收
  92. pthread_detach(id);
  93. //线程参数获取
  94. ThreadPool* argThis = (ThreadPool*)vo;
  95. while ( true)
  96. {
  97. argThis-> Lock();
  98. //如果任务队列为空 线程则一直等待
  99. //知道任务队列不为空则会被pushTask函数唤醒线程
  100. while (argThis-> QueueIsEmpty())
  101. {
  102. argThis-> Wait();
  103. }
  104. argThis-> MoveToBusy(id);
  105. cout << "工作前 任务数:" << argThis->taskQueue. size() << endl;
  106. cout << "工作前 busy:" << argThis->busyList. size() << endl;
  107. cout << "工作前 idle:" << argThis->idleList. size() << endl;
  108. cout << "-----------------------------------------------" << endl;
  109. //取任务
  110. BaseTask* task;
  111. task = argThis-> popTask(task);
  112. argThis-> Unlock();
  113. //任务工作
  114. task-> working();
  115. //工作结束
  116. argThis-> Lock();
  117. argThis-> MoveToIdle(id);
  118. argThis-> Unlock();
  119. cout << "工作完 任务数:" << argThis->taskQueue. size() << endl;
  120. cout << "工作完 busy:" << argThis->busyList. size() << endl;
  121. cout << "工作完 idle:" << argThis->idleList. size() << endl;
  122. cout << ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" << endl;
  123. }
  124. return nullptr;
  125. }

🌈ChildTask.h 

  • 子类配置 

  
  1. #pragma once
  2. #include "BaseTask.h"
  3. #include <iostream>
  4. #include <unistd.h>//sleep头文件
  5. using namespace std;
  6. class ChildTask :
  7. public BaseTask
  8. {
  9. public:
  10. ChildTask( char* data);
  11. ~ ChildTask();
  12. void working();
  13. };

🌈ChildTask.cpp

  • 子类设置延时模拟做任务的时间比较长 

  
  1. #include "ChildTask.h"
  2. ChildTask:: ChildTask( char* data) : BaseTask(data) //参数传给父类
  3. {
  4. }
  5. ChildTask::~ ChildTask()
  6. {
  7. }
  8. void ChildTask::working()
  9. {
  10. cout << this->data << "正在执行......" << endl;
  11. sleep( 3); //延时3秒 (模拟做业务的时间比较长)
  12. }

🌈BaseTask.h

  • 基类设置结构体来装业务 

  
  1. #pragma once
  2. #include <string.h>
  3. class BaseTask
  4. {
  5. public:
  6. BaseTask( char* data);
  7. ~ BaseTask();
  8. char data[ 1024]; //装业务
  9. virtual void working() = 0; //虚函数
  10. };

🌈BaseTask.cpp

  • 基类配置 

  
  1. #include "BaseTask.h"
  2. BaseTask:: BaseTask( char* data)
  3. {
  4. bzero( this->data, sizeof( this->data)); //清空
  5. memcpy( this->data, data, sizeof(data));
  6. }
  7. BaseTask::~ BaseTask()
  8. {
  9. delete this->data; //清除释放
  10. }

三、测试效果

  • 通过Linux连接VS进行跨平台编程,我们可以清晰的看到有几个线程是在做任务,几个线程是空闲的,整个过程就很清晰直观的展现出来了,如下动图所示: 

  • 10条线程做30个任务的全部记录,如下如所示:

四、总结

📌创建线程池的好处

  • 线程池的使用,能让我们搭建的高并发服务器真正意义上做到高并发
  • 降低资源消耗

        通过重复利用自己创建的线程降低线程创建和销毁造成的消耗

  • 提高响应速度

        当任务到达时,任务可以不需要等待线程创建和销毁就能立即执行

  • 提高线程的可管理性

        线程式稀缺资源,如果无限的创建线程,不仅会消耗资源,还会降低系统的稳定性

        使用线程池可以进行统一分配,调优和监控

以上就是本文的全部内容啦!如果对您有帮助,麻烦点赞啦!收藏啦!欢迎各位评论区留言!!!


转载:https://blog.csdn.net/m0_61745661/article/details/125560959
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场