目录
1.什么是线程池?
2.线程池的作用
3.任务队列的设计
4.构造函数
5.push接口设计
6.子线程的执行函数
7. 析构函数
8.测试线程池
9.线程池中的线程数量设定
1.经验值
2.最佳线程数目算法
线程池是一种 利用 池化技术思想 来实现的线程管理技术,主要是为了复用线程、便利地管理线程和任务、并将线程的 创建和任务的执行 解耦开来。我们可以创建线程池来复用已经创建的线程来降低频繁创建和销毁线程所带来的资源消耗。
线程池的任务队列可以数组,链表,队列,栈等容器来进行设计
我将任务队列 提前开辟一块连续的空间 作为 环形任务队列,不用频繁的去分配内存,减少内存碎片。
//任务队列
//T是任务
template
struct task_queue_t{
task_queue_t(int _count=QUEUECOUNT)
{
if(_count<=0){
throw "queue is zero";
}
max_count=_count;
cur_count=0;
queue=new T[max_count];
if(queue==nullptr)
{
throw bad_alloc();
}
head=0;
tail=0;
}
~task_queue_t()
{
if(queue!=nullptr){
delete queue;
queue=nullptr;
}
}
size_t head; //可读任务的位置
size_t tail; //可写任务的位置
size_t max_count;//最大的任务数量
size_t cur_count;//当前队列中的任务数量
T* queue;
};
template
threadpool::threadpool(size_t _thrd_count,size_t _task_size)
{
pool=new pthread_t[thrd_count];
if(pool==nullptr)
started=0;
task_queue=new task_queue_t(_task_size);
if(task_queue==nullptr)
{
throw bad_alloc();
}
pthread_mutex_init(&lock,NULL);
pthread_cond_init(&cond,NULL);
for(int i=0;i<_thrd_count;i++)
{
pthread_create(&pool[i],NULL,routine,this);
thrd_count++;
started++;
}
}
template
int threadpool::push(T* task){
if(pool==nullptr||task_queue==nullptr){
return -1;
}
if(pthread_mutex_lock(&lock)==-1){
return -1;
}
if(close){
pthread_mutex_unlock(&lock);
return -1;
}
if(task_queue->cur_count==task_queue->max_count){
pthread_mutex_unlock(&lock);
return -1;
}
task_queue->queue[task_queue->tail]=task;
if(pthread_cond_signal(&cond)!=0){
pthread_mutex_unlock(&lock);
return -1;
}
task_queue->tail=(task_queue->tail+1)%task_queue->max_count;
task_queue->cur_count++;
pthread_mutex_unlock(&lock);
return 0;
}
template
void* threadpool::routine(void* arg)
{
threadpool* pool=(threadpool*) arg;
while(true)
{
/*
1.加锁,进入请求池获取任务
2.如果没有任务,则阻塞在条件变量中,解锁
3.如果有任务,取出任务则运行该任务
*/
pthread_mutex_lock(&pool->lock);
while(pool->task_queue->cur_count==0&&pool->close==false)
{
pthread_cond_wait(&pool->cond,&pool->lock);
}
if(pool->close==true)break;
task_queue_t* queue=pool->task_queue;
T* t=queue->queue[queue->head];
pool->task_queue->head=(pool->task_queue->head+1)%pool->task_queue->max_count;
pool->task_queue->cur_count-=1;//任务数量-1
pthread_mutex_unlock(&pool->lock);
t->process();
}
//退出线程
pool->started--;//活跃线程减1
pthread_mutex_unlock(&pool->lock);
pthread_exit(NULL);
return NULL;
}
template
threadpool::~threadpool()
{
/*
1.先请求池中的任务全部处理完
2.在退出所有的线程
3.释放线程池资源
*/
if(pool==nullptr){
return;
}
if(pthread_mutex_lock(&lock)<0){
//加锁失败
m_errno=2;
return;
}
if(close==true)
{
thread_pool_free();
return;
}
close=true;
if(pthread_cond_broadcast(&cond)<0||pthread_mutex_unlock(&lock)<0){
thread_pool_free();
m_errno=3;
return;
}
wait_all_done();
thread_pool_free();
return;
}
template
int threadpool::wait_all_done()
{
int ret=0;
if(pool==nullptr){
return 1;
}
for(int i=0;i
int threadpool::thread_pool_free()
{
if(pool!=nullptr||started>0){
//还有活跃线程,不能还不能销毁资源
return -1;
}
delete[] pool;
pool=nullptr;
if(task_queue!=nullptr)
{
delete task_queue;
task_queue=nullptr;
}
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&cond);
started=0;
thrd_count=0;
}
为什么要需要先等子线程退出后,才释放线程池资源?
为了保证线程池中任务能够全部被执行完全,如果线程池还有线程在执行,如果主线程突然退出,会导致一些任务没有被执行,或者执行的不完全。
下面这段代码测试线程池中的线程是否可以并发执行任务。
struct task{
static int done ;
static pthread_mutex_t lock;
public:
void process(){
usleep(10000);
pthread_mutex_lock(&lock);
done++;
printf("%d:doing %d task\n",pthread_self(), done);
pthread_mutex_unlock(&lock);
}
};
int task::done = 0;
pthread_mutex_t task::lock;
int main()
{
threadpool* pool=new threadpool();
if (pool == NULL) {
printf("thread pool create error!\n");
return 1;
}
task* t=new task;
while (pool->push(t)==0) {
/// pthread_mutex_lock(&lock);
nums++;
// pthread_mutex_unlock(&lock);
t=new task;
}
printf("add %d tasks\n", nums);
pool->wait_all_done();
// printf("did %d tasks\n", done);
}
线程池中的线程数量设定 是需要 与cpu资源进行平衡选择
线程中的线程数量不是创建越多越好,因为一个cpu只能运行一个线程,创建线程池的目的是需要充分利用cpu资源。
怎样重复利用cpu资源?
当有些线程进行io阻塞等待的时候,此时是不会利用cpu资源的,这时候就可以切换到另一个线程,使cpu不断的进行运算。
配置线程数量之前,首先要看任务的类型是 IO密集型,还是CPU密集型?
什么是IO密集型?
比如:频繁读取磁盘上的数据,或者需要通过网络远程调用接口。
什么是CPU密集型?
比如:非常复杂的调用,循环次数很多,或者递归调用层次很深等。
如果是 混合型(既包含IO密集型,又包含CPU密集型)的如何配置线程数?
混合型如果 IO密集型,和CPU密集型 的执行时间相差不太大,可以拆分开,以便于更好配置。如果执行时间相差太大,优化的意义不大,比如IO密集型耗时60s,CPU密集型耗时1s。
除了上面介绍是经验值之外,其实还提供了计算公式:
最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目
因为很显然,线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程。