[Linux服务器] Linux —— 线程池

193 0
Honkers 2025-3-5 16:13:20 | 显示全部楼层 |阅读模式

目录

1.认识线程池

什么是线程池

线程池的优点

线程池的使用场景

2.线程池的实现

线程池的组成

线程池之间的关系 

线程池的实现代码

任务类型

线程池类型

主程序


1.认识线程池

什么是线程池

线程池(Thread Pool)是线程的一种使用方式。它通过预先创建一组线程并管理它们的生命周期,来提高多线程应用程序的性能和资源利用率。线程池的核心思想是减少在创建和销毁线程时所产生的开销

线程池的优点

  • 减少开销:线程的创建和销毁是有开销的,线程池通过复用线程减少了这种开销。

  • 提高响应速度:当任务到达时,线程池中已经有现成的线程可以立即执行任务,而不需要等待线程创建。

  • 提高线程的可控性:线程池可以统一管理线程的生命周期、数量和优先级,使得多线程编程更加可控。

  • 防止资源耗尽:通过限制线程的数量,线程池可以防止系统因创建过多线程而耗尽资源。

线程池的使用场景

1. Web 服务器处理请求:Web 服务器需要同时处理大量客户端请求,每个请求通常是一个短期任务(如 HTTP 请求)。使用线程池可以避免为每个请求创建和销毁线程的开销,提高服务器的响应速度和资源利用率。

2. 数据库连接池:数据库操作通常是 I/O 密集型任务,频繁创建和销毁数据库连接会消耗大量资源。通过线程池管理数据库连接,可以复用连接,减少创建和销毁连接的开销。

3. 异步任务处理:某些任务不需要立即执行,可以异步处理(如日志记录、邮件发送、消息推送等)。将任务提交到线程池中异步执行,避免阻塞主线程。

4. 批量数据处理:需要对大量数据进行处理(如文件解析、数据清洗、批量计算等),这些任务可以并行执行。将数据分片后提交到线程池中并行处理,提高处理效率。

关于线程池的应用还有很多,笔者就不一 一赘述了。

2.线程池的实现

线程池的组成

线程池内部主要有一个任务队列用于存储任务,还有一批线程从任务队列中获取任务并执行。任务队列中的任务通过一个控制线程传递进来,并且控制线程还要管理好线程池的生命周期,但是控制线程并不属于线程池

线程池之间的关系 

线程池代码中的角色有主线程和线程池中的从线程,主线程只有一个,从线程有多个,主线程往任务队列中放数据,从线程从任务队列中获取数据;此时的任务队列就是主线程和从线程之间的共享资源(也是临界资源)。因此,我们要讨论线程池中各角色之间的关系,只有讨论清楚关系了才能写出正确的并且健壮的代码。

  • 主线程和主线程之间 —— 主线程只有一个,不讨论。
  • 主线程和从线程之间 —— 互斥和同步:主线程在放数据的时候,从线程不能来获取数据,否则获取到的数据可能不完整,他们之间需要互斥的访问临界资源。而主线程生产数据之后,需要通知从线程来拿,任务队列中没有数据的时候,从线程需要进行排队等待,因此,他们之间具有同步关系。
  • 从线程和从线程之间 —— 互斥和同步:一个线程在拿数据,另一个线程就不能拿,否则可能拿到一样的数据,因此,他们之间需要互斥。为了避免某些进程竞争锁的能力太强导致的线程饥饿问题,当任务队列为空的时候,多个线程需要在条件变量下排队等待,因此,他们之间需要保持同步。

线程池的实现代码

我们设计的代码需要一个任务类型、一个线程池类型、一个主程序即可

任务类型

我们的任务类型只是简单的做一下加法即可,有想法的读者可以设计自己的任务类型。

  1. #include <iostream>
  2. #include <string>
  3. class Task
  4. {
  5. public:
  6. Task() {}
  7. Task(int num1, int num2) : _num1(num1), _num2(num2), _result(0)
  8. {
  9. }
  10. void Excute()
  11. {
  12. _result = _num1 + _num2;
  13. }
  14. void ResultToString() // 打印计算结果
  15. {
  16. std::cout << "Excute Task: " << std::to_string(_num1) + "+" + std::to_string(_num2) + "=" + std::to_string(_result) << std::endl;
  17. }
  18. std::string DebugToString() // 打印生产的任务
  19. {
  20. return std::to_string(_num1) + "+" + std::to_string(_num2) + "=?";
  21. }
  22. ~Task()
  23. {}
  24. private:
  25. int _num1;
  26. int _num2;
  27. int _result;
  28. };
复制代码

线程池类型

  1. #include <iostream>
  2. #include <queue>
  3. #include <pthread.h>
  4. #include <unistd.h>
  5. #include "task.hpp"
  6. #define THREAD_NUM 5
  7. template<class t="">
  8. class ThreadPool
  9. {
  10. private:
  11. int _thread_num; // 线程个数
  12. std::queue<t*> _task_queue; // 任务队列
  13. pthread_mutex_t _lock; // 互斥量,用于保护临界资源 —— 任务队列
  14. pthread_cond_t _cond; // 条件变量,维护线程池中的线程按照
  15. private:
  16. void LockQueue()
  17. {
  18. pthread_mutex_lock(&_lock);
  19. }
  20. void UnLockQueue()
  21. {
  22. pthread_mutex_unlock(&_lock);
  23. }
  24. void WakeUpOne()
  25. {
  26. pthread_cond_signal(&_cond);
  27. }
  28. void ThreadWait()
  29. {
  30. pthread_cond_wait(&_cond, &_lock);
  31. }
  32. bool IsEmptyQueue()
  33. {
  34. return _task_queue.empty();
  35. }
  36. static void *handler(void *arg)
  37. {
  38. ThreadPool* tp_ptr = (ThreadPool*)arg;
  39. while(true)
  40. {
  41. // 申请锁
  42. tp_ptr->LockQueue();
  43. // 判断任务队列是否为空 如果为空就让线程去等待
  44. while(tp_ptr->IsEmptyQueue())
  45. {
  46. tp_ptr->ThreadWait();
  47. }
  48. // 代码走到这里说明任务队列不为空
  49. Task *task; // 输出型参数,用于获取任务
  50. tp_ptr->GetTask(&task); // 获取任务
  51. tp_ptr->UnLockQueue(); // 释放锁
  52. task->Excute(); // 执行任务
  53. task->ResultToString(); // 打印执行结果
  54. delete task; // 任务是malloc的,使用完需要delete
  55. }
  56. return NULL;
  57. }
  58. public:
  59. ThreadPool(int num = THREAD_NUM)
  60. :_thread_num(num)
  61. {
  62. // 构造时初始化互斥量和条件变量
  63. pthread_mutex_init(&_lock, NULL);
  64. pthread_cond_init(&_cond, NULL);
  65. }
  66. ~ThreadPool()
  67. {
  68. // 析构时销毁互斥量和条件变量
  69. pthread_mutex_destroy(&_lock);
  70. pthread_cond_destroy(&_cond);
  71. }
  72. bool InitThreadPool() {
  73. pthread_t tid;
  74. // 创建一批线程
  75. for (int i = 0; i < _thread_num; i++)
  76. {
  77. int ret = pthread_create(&tid, NULL, handler, this);
  78. if (ret != 0)
  79. {
  80. std::cout<<"create pool thread error\n";
  81. return false;
  82. }
  83. }
  84. return true;
  85. }
  86. void PushTask(Task *task)
  87. {
  88. // 加锁
  89. LockQueue();
  90. // 向任务队列中放数据
  91. _task_queue.push(task);
  92. // 通知线程池中正在等待的线程来获取任务
  93. WakeUpOne();
  94. // 解锁
  95. UnLockQueue();
  96. }
  97. void GetTask(Task **task)
  98. {
  99. *task = _task_queue.front();
  100. _task_queue.pop();
  101. }
  102. };
复制代码

主程序

  1. #include "ThreadPool.hpp"
  2. int main()
  3. {
  4. // 定义线程池
  5. ThreadPool<task> thread_pool;
  6. // 初始化线程池
  7. if( thread_pool.InitThreadPool())
  8. {
  9. std::cout << "Create threadpool success" << std::endl;
  10. }
  11. srand(time(NULL));
  12. while(true) {
  13. // 主线程创建任务
  14. int num1 = rand()%1000;
  15. int num2 = rand()%1000;
  16. Task* task = new Task(num1, num2);
  17. // 打印创建的任务
  18. std::cout << "Create Task: " << task->DebugToString() << std::endl;
  19. // 向任务队列中放入任务
  20. thread_pool.PushTask(task);
  21. // 每个1秒创建一个线程
  22. sleep(1);
  23. }
  24. return 0;
  25. }
复制代码

运行结果:

  • 可以看到主线程创建的任务被线程池中的线程获取并执行了。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Honkers

特级红客

关注
  • 3159
    主题
  • 36
    粉丝
  • 0
    关注
这家伙很懒,什么都没留下!

中国红客联盟公众号

联系站长QQ:5520533

admin@chnhonker.com
Copyright © 2001-2025 Discuz Team. Powered by Discuz! X3.5 ( 粤ICP备13060014号 )|天天打卡 本站已运行