线程池
简介:
在多线程任务中为了更加有效的完成任务,我们在运行一个后台服务的时候希望更快的完成用户提交的任务。这里我们一般使用多线程的方式,当然我们可以一次性创建多个线程任务来了就直接分配,也可以当任务来的时候我们创建一个线程去完成对应的任务。无论哪种方式都是可以解决我们的问题,为了让程序更加有效,我们可以使用线程池的方式去完成。线程池会让我们的执行效率和空间利用率更高。以下是我们具体的实施方案。
数据结构
- 任务结构体
typedef struct
{
void (*function)(void *arg);
void *arg;
}threadpool_task_t;
该结构体中有两个变量分别是任务执行的函数指针有一个参数void *arg;另一个是需要传入的参数指针;
- 线程池
struct threadpool_t
{
pthread_mutex_t lock;
pthread_mutex_t thread_counter;
pthread_cond_t queue_not_full;
pthread_cond_t queue_not_empty;
pthread_t *threads;
pthread_t adjust_tid;
threadpool_task_t *task_queue;
int min_thr_num;
int max_thr_num;
int live_thr_num;
int busy_thr_num;
int wait_exit_thr_num;
int queue_front;
int queue_rear;
int queue_size;
int queue_max_size;
int shutdown;
};
这个结构体是我们自己定义的线程池。具体的变量含义如下:
变量名 | 含义解释 |
---|---|
lock | 锁住线程池的锁 |
thread_counter | 记录忙状态线程个数的锁 |
queue_not_full | 当任务队列满时,添加任务的线程阻塞,等待此条件变量 |
queue_not_empty | 任务队列里不为空时,通知等待任务的线程 |
*threads | 存放线程池中每个线程的线程ID |
adjust_tid | 管理者线程ID |
*task_queue | 任务队列指针 |
min_thr_num | 线程池最小线程数 |
max_thr_num | 线程池最大线程数 |
live_thr_num | 当前存活线程个数 |
busy_thr_num | 忙状态线程个数 |
wait_exit_thr_num | 需要销毁的线程个数 |
queue_front | 任务队列头下标 |
queue_rear | 任务队列尾下标 |
queue_size | 任务队列中实际任务数 |
queue_max_size | 任务队列中的最大任务数 |
shutdown | 标志线程池是否可用 true false |
程序简介
主程序
在主程序中我们首先要创建线程池struct threadpool_t *thp=threadpool_create(3,100,100);
这里我们要传递三个参数最小线程数、最大线程数和队列的最大任务数。
- 创建线程池
struct threadpool_t *pool=NULL;
pool=(struct threadpool_t*)malloc(sizeof(struct threadpool_t));
- 线程池创建成功后模拟产生任务并向线程池中添加任务。
for(i=0;i<20;++i){
num[i]=i;
printf("add task %d\n",i);
threadpool_add(thp,process,(void *)&num[i]);
}
- 程序最后销毁线程池
threadpool_destroy(thp);
线程池的初始化
我们通过定义函数struct threadpool_t* threadpool_create(int min_thr_num,int max_thr_num,int queue_max_size);
来初始化线程。
- 初始化线程池中的变量
pool->min_thr_num=min_thr_num;
pool->max_thr_num=max_thr_num;
pool->busy_thr_num=0;
pool->live_thr_num=min_thr_num;
pool->wait_exit_thr_num=0;
pthread_mutex_init(&(pool->lock),NULL)
...
通过赋值初始化变量也需要通过函数调用初始化锁和条件变量。
- 创建管理者线程
在初始化线程池的同时也需要创建管理者线程。
pthread_create(&(pool->adjust_tid),NULL,adjust_thread,(void *)pool);
- 创建失败则释放空间
threadpool_free(pool);
创建管理者线程
管理者线程主要负责对线程池的扩容和压缩即添加线程和销毁线程。首先判断线程池是否关闭如果未关闭则继续。sleep(DEFAULT_TIME);
是让管理者线程定时去管理线程池而无需一致运行管理。这里杀死线程是通过条件变量通知线程执行不存在的任务而杀死pthread_cond_signal(&(pool->queue_not_empty));
- 线程池的扩容
if (queue_size >= MIN_WAIT_TASK_NUM && live_thr_num < pool->max_thr_num)
{
pthread_mutex_lock(&(pool->lock));
int add = 0;
for (i = 0; i < pool->max_thr_num && add < DEFAULT_THREAD_VARY && pool->live_thr_num < pool->max_thr_num; ++i)
{
if (pool->threads[i] == 0 || !is_thread_alive(pool->threads[i]))
{
pthread_create(&(pool->threads[i]), NULL, threadpool_thread, (void *)pool);
++add;
++pool->live_thr_num;
}
}
pthread_mutex_unlock(&(pool->lock));
}
- 线程池的压缩
if ((busy_thr_num * 2) < live_thr_num && live_thr_num > pool->min_thr_num)
{
pthread_mutex_lock(&(pool->lock));
pool->wait_exit_thr_num = DEFAULT_THREAD_VARY;
pthread_mutex_unlock(&(pool->lock));
for (i = 0; i < DEFAULT_THREAD_VARY; ++i)
{
pthread_cond_signal(&(pool->queue_not_empty));
}
}
释放线程池空间
通过定义函数int threadpool_free(struct threadpool_t *pool);
来完成,释放空间时也需要销毁创建的锁和条件变量。
free(pool->threads);
pthread_mutex_lock(&(pool->lock));
pthread_mutex_destroy(&(pool->lock));
pthread_mutex_lock(&(pool->thread_counter));
pthread_mutex_destroy(&(pool->thread_counter));
pthread_cond_destroy(&(pool->queue_not_empty));
pthread_cond_destroy(&(pool->queue_not_full));
free(pool);
销毁线程池
通过调用函数threadpool_destroy完成任务,该函数需要传递一个待销毁的线程池指针。
- 判断线程池是否开启
pool->shutdown=true;
- 销毁管理者线程
pthread_join(pool->adjust_tid,NULL);
- 销毁其它线程
for (i = 0; i < pool->live_thr_num; ++i)
{
pthread_cond_broadcast(&(pool->queue_not_empty));
}
for (i = 0; i < pool->live_thr_num; ++i)
{
pthread_join(pool->threads[i], NULL);
}
- 最后释放线程池空间
threadpool_free(pool);
向线程池添加任务
通过函数int threadpool_add(struct threadpool_t *pool,void* (*function)(void *),void *arg)
实现。参数分别是线程池指针、任务函数指针和任务参数指针。
- 对线程池加锁
pthread_mutex_lock(&(pool->lock));
如果任务队列中的任务数已满或者线程池已经停用则阻塞条件等待任务队列中的任务被处理。
while ((pool->queue_size == pool->queue_max_size) && (!pool->shutdown))
{
pthread_cond_wait(&(pool->queue_not_full), &(pool->lock));
}
- 添加任务到队列
pool->task_queue[pool->queue_rear].function = function;
pool->task_queue[pool->queue_rear].arg = arg;
pool->queue_rear = (pool->queue_rear + 1) % pool->queue_max_size;
++pool->queue_size;
这里采用的是循环队列。将传递过来的任务函数和参数指针赋值给队尾任务。然后通知阻塞在queue_not_empty条件变量的线程处理任务。
pthread_cond_signal(&(pool->queue_not_empty));
线程创建成功后的回调函数
该函数命名为threadpool_thread。当一个普通线程被创建成功后会主动调用该函数执行相应的任务。
如果任务队列为空并且线程池没有关闭则阻塞在queue_not_empty
条件变量。
while ((pool->queue_size == 0) && (!pool->shutdown))
{
printf("thread %u is waiting\n", (unsigned int)pthread_self());
pthread_cond_wait(&(pool->queue_not_empty), &(pool->lock));
//清除指定数目的空闲线程,如果要结束的线程个数大于0,结束线程
if (pool->wait_exit_thr_num > 0)
{
pool->wait_exit_thr_num--;
//如果线程池中的线程个数大于最小值时可以结束当前线程
if (pool->live_thr_num > pool->min_thr_num)
{
printf("thread %u is exiting\n", (unsigned int)pthread_self());
pool->live_thr_num--;
pthread_mutex_unlock(&(pool->lock));
pthread_exit(NULL);
}
}
}
这里如果阻塞被成功唤醒后首先会执行一个清除空闲线程的操作即if语句内的操作。该操作的目的是线程池瘦身。如果是任务添加后导致执行该段代码实则不会执行,因为不满足if条件中的判断;如果是瘦身操作唤醒的则该段代码被选择执行(两个if)。如果线程池已经关闭则退出该线程:
if (pool->shutdown)
{
pthread_mutex_unlock(&(pool->lock));
printf("thread %u is exiting\n", (unsigned int)pthread_self());
pthread_detach(pthread_self());
pthread_exit(NULL);
}
接下来需要将任务从任务队列中取出,并修改任务环形队列的状态。
task.function = pool->task_queue[pool->queue_front].function;
task.arg = pool->task_queue[pool->queue_front].arg;
pool->queue_front = (pool->queue_front + 1) % pool->queue_max_size;
pool->queue_size--;
通知条件变量queue_not_full,让新产生的任务可以加入到任务队列。
pthread_cond_broadcast(&(pool->queue_not_full));
忙线程数增加;
pool->busy_thr_num++;
执行相应的任务;
task.function(task.arg);
任务完成后,忙线程数减少。
pool->busy_thr_num--;
执行任务函数
该函数通过添加任务时可以指定线程执行的任务函数。
void *process(void *arg){
printf("thread %u working on task %d\n",(unsigned int)pthread_self(),(int)arg);
sleep(1);
printf("task %d is end\n",*(int*)arg);
return NULL;
}
线程是否存活
该函数的作用用来判断给定线程ID是否存活。
int is_thread_alive(pthread_t tid){
int ret=pthread_kill(tid,0);
if(ret==0){
return true;
}
return false;
}
如果返回宏ESRCH
则该线程已经死亡,宏EINVAL
为信号不合法、0为线程存在。
完整代码
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<unistd.h>
#include<assert.h>
#include<string.h>
#include<signal.h>
#include<errno.h>
#define DEFAULT_TIME 10
#define MIN_WAIT_TASK_NUM 10
#define DEFAULT_THREAD_VARY 10
#define true 1
#define false 0
typedef struct
{
void (*function)(void *arg);
void *arg;
}threadpool_task_t;
struct threadpool_t
{
pthread_mutex_t lock;
pthread_mutex_t thread_counter;
pthread_cond_t queue_not_full;
pthread_cond_t queue_not_empty;//通知处理任务 有任务添加
pthread_t *threads;
pthread_t adjust_tid;
threadpool_task_t *task_queue;
int min_thr_num;
int max_thr_num;
int live_thr_num;
int busy_thr_num;
int wait_exit_thr_num;
int queue_front;
int queue_rear;
int queue_size;
int queue_max_size;
int shutdown;
};
struct threadpool_t* threadpool_create(int min_thr_num,int max_thr_num,int queue_max_size);
int threadpool_add(struct threadpool_t* pool,void* (*function)(void *),void *arg);
void *process(void *arg);
void *threadpool_thread(void *arg);
void *adjust_thread(void *threadpool);
int threadpool_destroy(struct threadpool_t* thp);
int is_thread_alive(pthread_t tid);
int threadpool_free(struct threadpool_t *pool);
int threadpool_busy_threadum(struct threadpool_t *pool);
int main(int argc, char const *argv[])
{
struct threadpool_t *thp=threadpool_create(3,100,100);
int num[290],i;
for(i=0;i<290;++i){
num[i]=i;
threadpool_add(thp,process,(void *)&num[i]);
}
sleep(100);
threadpool_destroy(thp);
return 0;
}
int threadpool_destroy(struct threadpool_t *pool){
int i;
if(pool==NULL){
return -1;
}
pool->shutdown=true;
//先销毁管理者线程
pthread_join(pool->adjust_tid,NULL);
for(i=0;i<pool->live_thr_num;++i){
pthread_cond_broadcast(&(pool->queue_not_empty));
}
for(i=0;i<pool->live_thr_num;++i){
pthread_join(pool->threads[i],NULL);
}
threadpool_free(pool);
return 0;
}
int threadpool_free(struct threadpool_t *pool){
if(pool==NULL){
return -1;
}
if(pool->threads){
free(pool->threads);
pthread_mutex_lock(&(pool->lock));
pthread_mutex_destroy(&(pool->lock));
pthread_mutex_lock(&(pool->thread_counter));
pthread_mutex_destroy(&(pool->thread_counter));
pthread_cond_destroy(&(pool->queue_not_empty));
pthread_cond_destroy(&(pool->queue_not_full));
}
free(pool);
pool=NULL;
return 0;
}
int threadpool_add(struct threadpool_t *pool,void* (*function)(void *),void *arg){
pthread_mutex_lock(&(pool->lock));
while ((pool->queue_size==pool->queue_max_size) && (!pool->shutdown))
{
pthread_cond_wait(&(pool->queue_not_full),&(pool->lock));
}
if(pool->shutdown){
pthread_cond_broadcast(&(pool->queue_not_empty));
pthread_mutex_unlock(&(pool->lock));
return 0;
}
//清空 工作线程 调用的回调函数的参数arg
if(pool->task_queue[pool->queue_rear].arg!=NULL){
pool->task_queue[pool->queue_rear].arg=NULL;
}
pool->task_queue[pool->queue_rear].function=function;
pool->task_queue[pool->queue_rear].arg=arg;
pool->queue_rear=(pool->queue_rear+1)%pool->queue_max_size;
++pool->queue_size;
pthread_cond_signal(&(pool->queue_not_empty));
pthread_mutex_unlock(&(pool->lock));
return 0;
}
struct threadpool_t* threadpool_create(int min_thr_num,int max_thr_num,int queue_max_size){
int i;
struct threadpool_t *pool=NULL;
do
{
if((pool=(struct threadpool_t*)malloc(sizeof(struct threadpool_t)))==NULL){
// printf("malloc threadpool failed\n");
break;
}
pool->min_thr_num=min_thr_num;
pool->max_thr_num=max_thr_num;
pool->busy_thr_num=0;
pool->live_thr_num=min_thr_num;
pool->wait_exit_thr_num=0;
pool->queue_size=0;
pool->queue_max_size=queue_max_size;
pool->queue_front=0;
pool->queue_rear=0;
pool->shutdown=false;
pool->threads=(pthread_t *)malloc(sizeof(pthread_t)*max_thr_num);
if(pool->threads==NULL){
// printf("malloc pool->threads failed\n");
break;
}
memset(pool->threads,0,sizeof(pthread_t)*max_thr_num);
pool->task_queue=(threadpool_task_t *)malloc(sizeof(threadpool_task_t)*queue_max_size);
if(pool->task_queue==NULL){
// printf("malloc pool->task_queue failed\n");
break;
}
if(pthread_mutex_init(&(pool->lock),NULL)!=0
||pthread_mutex_init(&(pool->thread_counter),NULL)!=0
||pthread_cond_init(&(pool->queue_not_empty),NULL)!=0
||pthread_cond_init(&(pool->queue_not_full),NULL)!=0){
// printf("init lock or cond failed\n");
break;
}
for(i=0;i<min_thr_num;++i){
pthread_create(&(pool->threads[i]),NULL,threadpool_thread,(void *)pool);
// printf("start thread %u...\n",(unsigned int)pool->threads[i]);
}
pthread_create(&(pool->adjust_tid),NULL,adjust_thread,(void *)pool);
return pool;
} while (0);
threadpool_free(pool);
return NULL;
}
void *process(void *arg){
// printf("thread %u working on task %d\n",(unsigned int)pthread_self(),(int)arg);
sleep(1);
printf("task %d is end\n",*(int*)arg);
return NULL;
}
void *threadpool_thread(void *threadpool){
struct threadpool_t *pool=(struct threadpool_t*)threadpool;
threadpool_task_t task;
while (true)
{
pthread_mutex_lock(&(pool->lock));
while ((pool->queue_size==0)&&(!pool->shutdown))
{
// printf("thread %u is waiting\n",(unsigned int)pthread_self());
pthread_cond_wait(&(pool->queue_not_empty),&(pool->lock));
//线程池瘦身的操作
//清除指定数目的空闲线程,如果要结束的线程个数大于0,结束线程
if(pool->wait_exit_thr_num>0){
pool->wait_exit_thr_num--;
//如果线程池中的线程个数大于最小值时可以结束当前线程
if(pool->live_thr_num > pool->min_thr_num){
printf("thread %u is exiting\n",(unsigned int)pthread_self());
pool->live_thr_num--;
pthread_mutex_unlock(&(pool->lock));
pthread_exit(NULL);
}
}
}
if(pool->shutdown){
pthread_mutex_unlock(&(pool->lock));
// printf("thread %u is exiting\n",(unsigned int)pthread_self());
pthread_detach(pthread_self());
pthread_exit(NULL);
}
task.function=pool->task_queue[pool->queue_front].function;
task.arg=pool->task_queue[pool->queue_front].arg;
pool->queue_front=(pool->queue_front+1)%pool->queue_max_size;
pool->queue_size--;
pthread_cond_broadcast(&(pool->queue_not_full));
pthread_mutex_unlock(&(pool->lock));
// printf("thread %u start working\n",(unsigned int)pthread_self());
pthread_mutex_lock(&(pool->thread_counter));
pool->busy_thr_num++;
pthread_mutex_unlock(&(pool->thread_counter));
task.function(task.arg);
// printf("thread %u end working\n",(unsigned int)pthread_self());
pthread_mutex_lock(&(pool->thread_counter));
pool->busy_thr_num--;
pthread_mutex_unlock(&(pool->thread_counter));
}
pthread_exit(NULL);
}
void* adjust_thread(void *threadpool){
int i;
struct threadpool_t *pool=(struct threadpool_t*)threadpool;
while (!pool->shutdown)
{
sleep(DEFAULT_TIME);
pthread_mutex_lock(&(pool->lock));
int queue_size=pool->queue_size;
int live_thr_num=pool->live_thr_num;
pthread_mutex_unlock(&(pool->lock));
pthread_mutex_lock(&(pool->thread_counter));
int busy_thr_num=pool->busy_thr_num;
pthread_mutex_unlock(&(pool->thread_counter));
if(queue_size>=MIN_WAIT_TASK_NUM&&live_thr_num<pool->max_thr_num){
pthread_mutex_lock(&(pool->lock));
int add=0;
for(i=0;i<pool->max_thr_num&&add<DEFAULT_THREAD_VARY&&pool->live_thr_num<pool->max_thr_num;++i){
if(pool->threads[i]==0||!is_thread_alive(pool->threads[i])){
pthread_create(&(pool->threads[i]),NULL,threadpool_thread,(void *)pool);
++add;
++pool->live_thr_num;
}
}
pthread_mutex_unlock(&(pool->lock));
}
if((busy_thr_num*2)<live_thr_num && live_thr_num>pool->min_thr_num){
pthread_mutex_lock(&(pool->lock));
pool->wait_exit_thr_num=DEFAULT_THREAD_VARY;
pthread_mutex_unlock(&(pool->lock));
for(i=0;i<DEFAULT_THREAD_VARY;++i){
pthread_cond_signal(&(pool->queue_not_empty));
}
}
}
return NULL;
}
int is_thread_alive(pthread_t tid){
int ret=pthread_kill(tid,0);
if(ret==0){
return true;
}
return false;
}