线程池设计

文章介绍了线程池的概念,它是如何通过维护一组线程来避免频繁创建和销毁线程的开销,适用于处理大量短时任务的场景。接着,文章给出了一个C++实现线程池的代码示例,包括线程池类的设计,以及如何将任务放入队列由线程池中的线程执行。最后,文章指出线程池在Web服务器等高并发场景中的应用价值。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Linux——线程池设计


位图 (11)

概念

线程池是一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。

应用场景

  1. 需要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个 Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。
  2. 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
  3. 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限, 出现错误

代码实现

实现目标:

    1. 创建固定数量线程池,循环从任务队列中获取任务对象
    2. 获取到任务对象后,执行任务对象中的任务接口

image-20230728095526814

  • 主线程创建好线程池,主线程负责把任务放进队列。队列中没用任务,线程池中的线程就阻塞挂起,队列中有任务,线程池里的线程就去执行。本质上是单生产者多消费者模型

threadpool.hpp

 1 #pragma once                                                                                                                       
  2 #include<pthread.h>
  3 #include<vector>
  4 #include<queue>
  5 #include<mutex>
  6 #include<string>
  7 #include<unistd.h>
  8 #include"mythread.hpp"
  9 #include"mutex.hpp"
 10 #include"task.hpp"
 11 
 12 using namespace std;
 13 template<class T>
 14 class threadPool;
 15 
 16 template<class T>
 17 class threadData
 18 {
 19 public:
 20 threadPool<T>* _tpp;//指向threadpool的指针
 21 string tpp_name;//线程名
 22 
 23 threadData(threadPool<T>*tpp,const string& tppname )
 24 :_tpp(tpp)
 25 ,tpp_name(tppname)
 26 {
 27     //noting
 28 }
 29 };
 30 
 31 
 32  const int gnum=3;
 33  template<class T>
 34 class threadPool
 35 {
 36 private:
 37 
 38 static void* handlertask(void* args)
 39 {
 40     threadData<T>* tdp=static_cast<threadData<T>*>(args);
 41     while(true)
 42     {
 43     T t;
 44     {LockReady lr(tdp->_tpp->Mutex());//加锁
 45     while(tdp->_tpp->isqueueempty())
 46     {
 47      tdp->_tpp->condwait();//如果队列为空,则线程需要在条件变量处等待
 48     }
 49   t=  tdp->_tpp->pop();//从队列中取数据                                                                                            
 50 
 51     }
 52     cout<<"threadname: "<<tdp->tpp_name<<"get task: "<<t.taskstringforP()<<"and ret: "<<t()<<endl;
 53     }
 54 
55 }
 56     threadPool(const int num=gnum):_num(num)
 57     {
 58         pthread_mutex_init(&_mut,nullptr);//互斥量的初始化
 59         pthread_cond_init(&_cond,nullptr);//条件变量的初始化
 60         for(int i=0;i<_num;i++)
 61         {
 62           _threads.push_back(new thread());//创建新线程对象并放到数组里面
 63         }
 64     }
 65 
 66 public:
 67 
 68 void lockqueue(){pthread_mutex_lock(&_mut);}//加锁
 69 void unlockqueue(){pthread_mutex_unlock(&_mut);}//解锁
 70 void condwait(){pthread_cond_wait(&_cond,&_mut);}//线程在条件变量处等待
 71 bool isqueueempty(){return _queue.empty();}//判断队列是否为空
 72 
 73 void push(const T& in)
 74 {
 75     LockReady lr(&_mut);//加锁
 76     _queue.push(in);//把对象放进队列                                                                                               
 77     pthread_cond_signal(&_cond);//唤醒在条件变量处阻塞的线程
 78 }
 79 
 80 T pop()
 81 {
 82     T t=_queue.front();//取队列头的对象--拷贝构造
 83     _queue.pop();
 84     return t;
 85 }
 86 
 87 ~threadPool()
 88 {
 89     pthread_cond_destroy(&_cond);//销毁条件变量
 90     pthread_mutex_destroy(&_mut);//销毁锁
 91     for(const auto& thr :_threads)
 92     {
 93         delete thr;//销毁线程对象
 94     }
 95 }
 96 
 97 pthread_mutex_t *Mutex(){return &_mut;}//供static函数调用
 98 
 99 void run()//遍历数组中的线程,逐个完成创建
100 {
101     for(const auto& th:_threads)
102     {
103         threadData<T>*td=new threadData<T>(this,th->threadname());                                                                 
104 //第一个参数是指向threadPool的指针,第二个是在thread类中调用的拿取线程名的函数
105         th->start(handlertask,td);//第一个传需要调用的函数(任务),第二个传指向threadData对象的指针
106      }
107 }
108 
109 static threadPool<T>*getthpptr()
110 {
111     if(nullptr==tp)
112     {
113         _Clock.lock();//加锁,指向threadpool的指针只有一个且线程都能访问,因此需要用锁来维护,这里是用的C++的锁
114         if(nullptr==tp)
115         {
116             tp=new threadPool<T>();
117         }
118         _Clock.unlock();//解锁
119     }
120 return tp;
121 }
122 
123 private:
124 int _num;//用来标记线程名
125 pthread_mutex_t _mut;//锁
126 pthread_cond_t _cond;//条件变量
127 vector<thread*> _threads;//存储线程的数组
128 queue<T> _queue;//存放计算任务的队列
129                                                                                      
130 static threadPool<T>* tp;//类内声明
131 static std::mutex _Clock;//C++中的锁-类内声明
132 };
133 template<class T>
134 threadPool<T>* threadPool<T>::tp=nullptr;//类外定义
135 template<class T>
136 std::mutex threadPool<T>::_Clock;//类外定义
  • 第38行handlertask函数若作为类的成员函数,那么在传参时内含this指针,会导致传参出现问题。因此handlertask用的static修饰,意味着该函数在threadPool类中只有一份,且被该类的所有对象所共享,存放在静态区。加上该函数的作用是判断队列为空,若为空则挂起,不为空就把队列中的任务取出来。因此该函数可能会被多个执行流进入,所以在函数内需要加锁,用互斥量来维护。该函数在threadPool类中是需要频繁被调用的,且为了实现出多消费者阻塞式完成消费任务也必须用互斥量来维护临界资源。
  • 第99行run函数供外部调用,负责把初始化threadData对象,并且把参数handlertask函数和指向threadData对象的指针传给start函数
  • 第23行threadData类含threadPool类的指针和字符串tpp_name
  • 第109行getthpptr函数负责供外部调用获取指向threadPool的指针,其中函数成员_Clock是C++库中的锁

mythread.hpp

 1 #pragma once                                                                                                                     
    2 #include<iostream>
    3 #include<pthread.h>
    4 #include<string.h> 
    5 #include<functional>
    6 #include<assert.h>
    7 using namespace std;
    8 
    9 class thread
   10 {
   11   private:
   12   static void* start_rontine(void* args)//传递this指针过来
   13 {
   14 thread* ts=static_cast<thread*>(args);
   15 ts->callback();//调用函数,该函数调用外部传进来的函数
W> 16 }
   17 public:
   18 typedef  function<void* (void*)> func_t;//包装器构建返回值类型为void* 参数类型为void* 的函数类型
   19 const int num=1024;
   20  thread()//构造函数--只负责初始化线程名,不负责创建线程,创建线程的函数是start
   21  {
   22 char namebuffer[num];
   23 snprintf(namebuffer,sizeof namebuffer,"thread-%d",number++);//缓冲区内保存线程的名字即几号线程
   24  name_=namebuffer;
   25  }
   26 
   27 void start(func_t fun,void* args=nullptr)//创建线程
   28 {
  29   fun_=fun;//外面传进来的需要调用的函数
   30   args_=args;//外面传进来需要给调用的函数传的参数
   31   int n=pthread_create(&pid_,nullptr,start_rontine,this);//因为调用函数start_rontine是类内函数,具有缺省参数this指针,在后续解包>
      参数包会出问题,所以需要一个类来直接获取函数参数
   32 assert(n==0);
   33 (void)n;
   34 }
   35 
   36 void* callback(){return fun_(args_);}//调用外部传进来的函数
   37 
   38 void join()
   39 {
   40   int n=  pthread_join(pid_,nullptr);
   41   assert(n==0);
   42   (void)n;
   43 }
   44 
   45 string threadname()//供外部去调用
   46 {
   47   return name_;
   48 }
   49                                                                                                                                  
   50 ~thread()
   51 {
   52     //
   53 }
   54 
   55     private:
   56     string name_;//线程的名字
   57     pthread_t pid_;//线程id
   58   func_t fun_;//线程调用的函数对象
   59   void* args_;//线程调用的函数的参数
   60   static int number;//类中声明
   61 
   62 };
   63 
   64 int thread::number=1;//类外定义
  • 第12行start_rontine函数使用static修饰的原因是成员函数参数内含this指针,转化参数类型时会出问题
  • 第27行start函数的参数fun是threadpool.hpp中的threadPool类的handlertask函数,参数args是threadData对象的字符串tpp_name,而该字符串是45行threadname函数传过去的name_字符串
  • 第31行pthread_create创建线程调用的是start_rontine函数,传給该函数的参数是指向thread对象的指针,而start_rontine函数负责调用callback函数,callback函数负责调用fun_函数,并把args _参数传递过去,实际上是把线程名name _传递給threadpool.hpp的handlertask函数

main.cc

 1 #include<iostream>                                                                                                                 
  2 #include<pthread.h>
  3 #include<time.h>
  4 #include<sys/types.h>
  5 #include<unistd.h>
  6 #include"threadpool.hpp"
  7 #include"task.hpp"
  8 using namespace std;
  9 
 10 int main()
 11 {
 12     srand((unsigned int)time(nullptr)^getpid());
 13     threadPool<Caltask>::getthpptr()->run();
 14 while(true)
 15 {
 16    int x=rand()%100;
 17     int y=rand()%10;
 18 int num=rand()%oper.size();
 19 Caltask t(x,y,oper[num],mymath);
 20 
 21 threadPool<Caltask>::getthpptr()->push(t);//把任务放进队列中
 22 cout<<"push task in queue"<<endl;
 23 sleep(1);
 24 }
 25     return 0;
 26 }

task.hpp

    1 #pragma once                                                      
    2 #include<stdlib.h>
    3 #include<functional>
    4 using namespace std;
    5 class Caltask
    6 {
    7     typedef function<int(int,int,char)> fun_c;
    8 public:
    9 Caltask(){}//无参构造
   10 
   11 Caltask(int x,int y,char op,fun_c func)
   12 :_x(x)
   13 ,_y(y)
   14 ,_op(op)
   15 ,_caltask(func)
   16 {}
   17   
   18 string operator()()//()运算符重载
   19 {
   20 int ret=_caltask(_x,_y,_op);//调用外部传进来的计算任务
   21 char buffer[128];
   22 snprintf(buffer,sizeof buffer,"%d %c %d =%d ",_x,_op,_y,ret);
   23 return buffer;
   24 }
   25  
   26 string taskstringforP()
   27 {
   28     char buffer[128];
   29     snprintf(buffer,sizeof buffer,"%d %c %d =?",_x,_op,_y);
   30     return buffer;
   31 }
   32 
   33 private:
   34 int _x;//参数一
   35 int _y;//参数二
   36 char _op;//运算符号
   37 fun_c _caltask;//需调用的外部计算函数
   38 };
   39 
   40 
   41 class SaveTask
   42 {
   43     typedef function<void(string)> fun_c;
   44 public:
   45 SaveTask(){}//默认构造
   46 
   47 SaveTask(  string & s,fun_c func)
   48 :_str(s)
   49 ,_func(func)
   50 {}                                                                                                                               
   51 
   52 void operator()()
   53 {
   54     _func(_str);
   55 }
   56
   57 private:
   58 string _str;
   59 fun_c _func;
   60 };
   61 
   62 void tosave(const string&s)
   63 {
   64     string target="./log.txt";//文件的路径
   65     FILE*fp=fopen(target.c_str(),"a+");// 以追加的方式打开文件
   66     if(!fp)//文件打开失败
   67     {
   68         cout<<"fopen error"<<endl;
   69     }
   70 
   71  fputs(s.c_str(),fp);//往文件里面写数据
   72  fputs("\n",fp);// 往文件里面写换行符
   73  fclose(fp);//关闭文件
   74 }
   75 
   76 const string oper="+-*/%";
   77 int mymath(int x,int y,char op)                                                                                                  
   78 {
   79     int ret=0;
   80     switch(op)
   81     {
   82         case '+':
   83        ret=x+y;
   84         break;
   85 
   86         case '-':
   87         ret=x-y;
   88         break;  
   89 
   90         case '*':
   91         ret=x*y;
   92         break;        
   93         
   94         case '/':
   95         if(y==0)
   96         {
   97             cerr<<"div zero erro!"<<endl;
   98             ret=-1;
   99         }else
  100         {
  101             ret=x/y;
  102         }
  103         break;                                                                                                                   
  104         
  105         case '%':
  106         if(y==0)
  107         {
  108             cerr<<"div zero erro!"<<endl;
  109             ret=-1;
  110         }else
  111         {
  112             ret=x%y;
  113         }
  114         break;
  115 
  116         default:
  117         //do nothing
  118         break;
  119     }
  120     return ret;
  121 } 

mutex.hpp

 1 #pragma once                                                                                                                       
  2 #include<iostream>
  3 using namespace std;
  4 
  5 class Mutex{
  6 public:
  7 Mutex(pthread_mutex_t * mutex=nullptr):mutex_(mutex){}
  8 void Lock()
  9 {
 10     if(mutex_)
 11     {
 12         pthread_mutex_lock(mutex_);//加锁
 13     }
 14     
 15 }
 16 
 17 void UnLock()
 18 {
 19     if(mutex_)
 20     {
 21  pthread_mutex_unlock(mutex_);//解锁
 22     }
 23    
 24 }
 25 private:
 26 pthread_mutex_t *mutex_;
 27 
 28 };
 29 
 30 class LockReady
 31 {
 32     public:
 33     LockReady(pthread_mutex_t* mutex)
 34     :mutex_(mutex)
 35     {
 36 mutex_.Lock();//加锁
 37     }
 38     ~LockReady()
 39     {
 40         mutex_.UnLock();//解锁
 41     }
 42     public:
 43   Mutex mutex_;
 44 };

运行效果

image-20230728114259464

  • 主线程放一个任务进队列,线程池内的线程随机线程执行任务
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值