线程池:
一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。
线程池的应用场景:
1. 需要大量的线程来完成任务,且完成任务的时间比较短。
2. 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
3. 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误。
线程池示例:
1. 创建固定数量线程池,循环从任务队列中获取任务对象,
2. 获取到任务对象后,执行任务对象中的任务接口
1.线程池 = 一个线程安全队列 + 一大堆线程
2.使用:
2.1.线程池提供一个push接口,用来支持请求入队操作
2.2.线程池当中的线程从队列当中获取数据,进行处理
3.线程池当中的线程都是等价的,逻辑上可以认为是消费线程。所有的线程调用的都是一个入口函数
4.线程安全队列当中的元素 = 待处理的数据 + 处理的函数
模拟实现:
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <iostream>
#include <queue>
//线程池 = 线程安全队列 + 一大堆的线程
//线程安全队列
// 元素 = 数据 + 处理数据的函数地址
#define THREADCOUNT 4
typedef void* (*Handler_t)(int);
class ThreadTask
{
public:
ThreadTask(int data, Handler_t handler)
{
data_ = data;
handler_ = handler;
}
~ThreadTask()
{
//
}
//使用函数,处理数据
void Run()
{
handler_(data_);
}
private:
int data_;
Handler_t handler_; //返回值为void* 参数为int
};
class ThreadPool
{
public:
ThreadPool()
{
capacity_ = 10;
thread_capacity_ = THREADCOUNT;
pthread_mutex_init(&lock_, NULL);
pthread_cond_init(&cond_, NULL);
//创建线程
bool is_create = ThreadCreate();
if(!is_create)
{
printf("Threadpool Create thread failed\n");
exit(1);
}
IsExit = false;
cur_thread_count_ = THREADCOUNT;
}
~ThreadPool()
{
}
bool Push(ThreadTask* tt)
{
pthread_mutex_lock(&lock_);
if(IsExit)
{
pthread_mutex_unlock(&lock_);
return false;
}
que_.push(tt);
pthread_mutex_unlock(&lock_);
//当插入数据之后 通知线程池当中的线程(消费者)
pthread_cond_signal(&cond_);
return true;
}
bool Pop(ThreadTask** tt)
{
*tt = que_.front();
que_.pop();
return true;
}
void ThreadJoin()
{
for(int i = 0; i < THREADCOUNT; i++)
{
pthread_join(tid_[i], NULL);
}
}
// 如果直接退出线程,则有可能队列当中当中还有数据没有处理完毕;
// 我们不应该去调用这样的接口来结束我们的线程
//void ThreadExit()
//{
//for(int i = 0; i < THREADCOUNT; i++)
//{
//pthread_cancel(tid_[i]);
//}
//}
void ThreadPoolClear()
{
//标志位
pthread_mutex_lock(&lock_);
IsExit = true;
pthread_mutex_unlock(&lock_);
if(cur_thread_count_ > 0)
{
pthread_cond_broadcast(&cond_);
}
}
private:
static void* ThreadStart(void* arg)
{
ThreadPool* tp = (ThreadPool*)arg;
while(1)
{
//从队列当中获取数据,进行消费 对于不同的线程而言,在获取数据的时候,是互斥的
pthread_mutex_lock(&tp->lock_);
ThreadTask* tt;
while(tp->que_.empty())
{
//可以进行结束
if(tp->IsExit)
{
//退出
tp->cur_thread_count_--;
pthread_mutex_unlock(&tp->lock_);
pthread_exit(NULL);
}
//调用条件变量 等待接口
pthread_cond_wait(&tp->cond_, &tp->lock_);
}
tp->Pop(&tt);
pthread_mutex_unlock(&tp->lock_);
//调用队列当中元素提供的函数,去处理数据 对于线程走到该位置的时候,就可以并行的处理了
tt->Run();
//防止内存泄漏
delete tt;
}
}
bool ThreadCreate()
{
for(int i = 0; i < THREADCOUNT; i++)
{
int ret = pthread_create(&tid_[i], NULL, ThreadStart, (void*)this);
if(ret != 0)
{
perror("pthread_create");
return false;
}
}
return true;
}
private:
std::queue<ThreadTask*> que_;
size_t capacity_;
//互斥
pthread_mutex_t lock_;
//同步 消费线程的条件变量,但是并没有生产线程的条件变量
//由于客户端的请求行为我们是无法控制的。所以就不需要通知生产者来进行生产,当生产线程有了数据,就直接往线程池当中抛入就可以了,在通知消费线程来进行消费
pthread_cond_t cond_;
//线程池当中的初始化的时候线程数量
size_t thread_capacity_;
//标识具体还有多少线程数量
size_t cur_thread_count_;
//保存线程池当中的线程的线程标识符
pthread_t tid_[THREADCOUNT];
//标志是否可以退出
bool IsExit;
};
void* DealData(int data)
{
printf("consume data is %d\n", data);
return NULL;
}
int main()
{
ThreadPool* tp = new ThreadPool();
//在这个代码当中main函数的线程,就充当生产线程,往线程池的线程安全队列当中push数据
for(int i = 1; i <= 50; i++)
{
ThreadTask* tt = new ThreadTask(i, DealData);
tp->Push(tt);
}
//等待线程池当中线程退出
sleep(15);
tp->ThreadPoolClear();
tp->ThreadJoin();
delete tp;
return 0;
}
线程池线程退出
如果直接退出,可能线程池当中安全队列还有数据没有处理完成
有如下几种情况:
1.加互斥锁--》加锁--》判断是否为空
2.调用pthread_wait--》阻塞在pthread_cond_wait接口中
3.在队列中获取数据--》获取成功--》处理数据--》继续进入循环--》加锁--》判断.....
4.正在处理队列中数据--》处理数据--》继续进入循环--》加锁--》判断.....
只有在线程判断了当前队列中没有数据才可以退出