线程池
- 概念
- 为什么用线程池
- 应用场景
- 实现一个线程池
概念
线程的池子,有很多线程,但是数量不会超过池子的限制。(需要用到多执行流并行进行任务处理的时候,就从池子中取出)
一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络 sockets等的数量。
为什么用线程池
有大量的数据处理请求,需要多执行流并发/并行处理
若是一个数据请求的到来伴随一个线程的创建去处理,则会产生一些风险以及一些不必要的消耗:
- 线程若不限制数量的创建,在峰值压力下,线程创建过多,资源耗尽,有程序崩溃的风险
- 处理一个任务的时间:创建线程时间t1+任务处理时间t2+线程销毁时间t3 = T。若t2/T比例占据不够高,则表示大量的资源用于线程的创建与销毁成本上,因此线程池使用已经创建好的线程进行循环任务处理,就避免了大量线程的频繁创建于销毁的时间成本.
应用场景
- 需要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。
- 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
- 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误.
实现一个线程池
大量线程(每个线程中都是进行循环的任务处理)+ 任务缓冲队列
- 线程的入口函数,都是在创建线程的时候,就固定传入的,导致线程池中的线程进行任务处理的方式过于单一
- 因为线程的入口函数都是一样的,处理流程也就都是一样的,只能处理单一方式的请求。 - 灵活性太差
若任务队列中的任务,不仅仅是单纯的数据,而是包含任务处理方式在内的数据,这时候,线程池中的线程只需要使用传入的方式,处理传入的数据即可,不需要关心是什么数据,如何处理。 - 提高线程池的灵活性
typedef void (*_handler)(int data);
class MyTask{
private:
int _data; // 要处理的数据
handle_t handler; // 处理数据的方法
public:
SetTask(int data, handler_t handler); // 让用户自己传入要处理的数据和方法,组织出一个任务节点
Run(){
return _handle(_data);
}
};
每个线程的入口函数中,只需要不断的获取任务节点,调用任务节点中的Run接口就可以实现任务的处理。
#include <iostream>
#include <cstdio>
#include <queue>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#define MAX_THREAD 5
typedef void (*handler_t)(int);
class ThreadTask{
public:
ThreadTask():_data(-1), _handler(NULL)
{
}
void SetTask(int data, handler_t handler){
_data = data;
_handler = handler;
}
void Run(){ // 外部只需要调用Run,不需要关心任务如何处理
return _handler(_data);
}
private:
int _data;
handler_t _handler;
};
class ThreadPool{
public:
ThreadPool(int max_thr = MAX_THREAD):
_thr_max(max_thr)
{
pthread_mutex_init(&_mutex, NULL);
pthread_cond_init(&_cond, NULL);
for(int i = 0; i < _thr_max; i++){
pthread_t tid;
int ret = pthread_create(&tid, NULL, thr_start, this);
if(ret != 0){
printf("thread create error\n");
exit(-1);
}
}
}
~ThreadPool(){
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond);
}
bool TaskPush(ThreadTask &task){
pthread_mutex_lock(&_mutex);
_queue.push(task);
pthread_mutex_unlock(&_mutex);
pthread_cond_broadcast(&_cond); // 入队后唤醒所有线程,谁抢到谁处理
return true;
}
// 类的成员函数,有一个隐藏的默认参数(this指针)
// 线程的入口函数只有一个参数,所以设置为static
static void *thr_start(void * arg){
ThreadPool *p = (ThreadPool *) arg;
// 不断的从任务队列中取出任务,执行任务的Run接口就可以
while(1){
pthread_mutex_lock(&p->_mutex);
while(p->_queue.empty()){
pthread_cond_wait(&p->_cond, &p->_mutex);
}
ThreadTask task;
task = p->_queue.front();
p->_queue.pop();
pthread_mutex_unlock(&p->_mutex);
task.Run(); // 任务的处理要放在解锁之外,因为当前的锁保护的是队列的操作
}
return NULL;
}
private:
int _thr_max; // 线程池中线程的最大数量 - 根据这个初始化创建指定数量的线程
std::queue<ThreadTask> _queue;
pthread_mutex_t _mutex; // 保护队列操作的互斥锁
pthread_cond_t _cond; // 实现从队列中获取节点的同步条件变量
};
void test_func(int data){
int sec = (data % 3) + 1;
printf("tid:%p -- get data:%d , sleep:%d\n", pthread_self(), data, sec);
sleep(sec);
}
void tmp_func(int data){
printf("tif:%p -- tmp_func\n", pthread_self());
sleep(1);
}
int main(){
ThreadPool pool;
for(int i = 0; i < 10; i++){
ThreadTask task;
if(i % 2 == 0)
task.SetTask(i, test_func);
else
task.SetTask(i, tmp_func);
pool.TaskPush(task);
}
sleep(1000);
return 0;
}
一次运行结果:
如果本篇博文有帮助到您,留个赞激励一下博主呐~~