Linux——线程池设计

概念
线程池是一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。
应用场景
- 需要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个 Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。
- 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
- 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限, 出现错误
代码实现
实现目标:
- 创建固定数量线程池,循环从任务队列中获取任务对象
- 获取到任务对象后,执行任务对象中的任务接口
- 主线程创建好线程池,主线程负责把任务放进队列。队列中没用任务,线程池中的线程就阻塞挂起,队列中有任务,线程池里的线程就去执行。本质上是单生产者多消费者模型
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 };
运行效果
- 主线程放一个任务进队列,线程池内的线程随机线程执行任务