线程池的初步理解
声明
线程池类型
struct threadpool_t/*数据类型*/
{
pthread_mutex_t lock; /*用于所著本结构体*/
pthread_mutex_t thread_counter; /*记录忙状态线程个数的锁--busy_thr_num*/
pthread_cond_t queue_not_full; /*当任务队列满时,添加任务的线程阻塞,等待此条件的满足*/
pthread_cond_t queue_not_empty; /*任务队列里不为空时,通知等待任务的线程,*/
pthread_t *threads; /*存放线程池中每个线程的tid,数组*/
pthread_t adjust_tid; /*存管理线程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; /*task_queue对头下标*/
int queue_rear; /*tadk_queue队尾下标*/
int queue_size; /*task_queue队中实际任务数*/
int queue_max_size; /*task_queue队列可容纳任务数上限*/
int shutdown; /*标志位,线程池使用状态,true或false*/
};
任务节点
typedef struct
{
//任务队列实质是给线程设置回调函数
void *(*function)(void*);/*函数指针,回调函数*/
void *arg; /*上面函数的参数*/
}threadpool_task_t;//匿名结构体别名
主要行为
1、创建实例
2、添加新的任务
2、工作线程执行接口
3、管理者线程执行接口
4、判断线程是否存活
5、资源释放
6、返回线程个数
7、返回忙线程个数
执行入口
void test01()
{
/*创建线程池池里最小3个线程,最大1000个线程,队列最大100*/
threadpool_t *thp = threadpool_create(3, 1000, 100);
printf("pool inited\n");
int num[110], i;
for (i = 0; i < 110; i++)
{
num[i] = i;
printf("add task %d\n", i);
threadpool_add(thp, process, (void*)&num[i]);
}
printf("子线程完工\n");
printf("现在的线程个数:%d\n", threadpool_all_thread_num(thp));
printf("现在的忙线程个数:%d\n", threadpool_busy_thread_num(thp));
printf("主线程结束前\n");
sleep(8);/*等待子线程完成任务*/
printf("子线程完工\n");
printf("现在的线程个数:%d\n", threadpool_all_thread_num(thp));
printf("现在的忙线程个数:%d\n", threadpool_busy_thread_num(thp));
printf("主线程结束前\n");
sleep(20);/*等待子线程完成任务*/
printf("子线程完工\n");
printf("现在的线程个数:%d\n", threadpool_all_thread_num(thp));
printf("现在的忙线程个数:%d\n", threadpool_busy_thread_num(thp));
printf("主线程结束前\n");
sleep(30);/*等待子线程完成任务*/
printf("子线程完工\n");
printf("现在的线程个数:%d\n", threadpool_all_thread_num(thp));
printf("现在的忙线程个数:%d\n", threadpool_busy_thread_num(thp));
printf("主线程结束前\n");
threadpool_destroy(thp);
}
主要从创建实例的函数进行理解
threadpool_t *thp = threadpool_create(3, 1000, 100);
threadpool_t * threadpool_create(int min_thr_num, int max_thr_num, int queue_max_size)
{
/*初始化线程池*/
int i;
threadpool_t *pool = NULL;
do/*配合break语句类似于goto语句的用法*/
{
if ((pool = (threadpool_t*)malloc(sizeof(threadpool_t))) == NULL)
{
printf("malloc threadpool fail");
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->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 threads fail");
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 task_queue faile");
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 the lock or cond failed");
break;
}
//启动线程初始值
for (i = 0; i < min_thr_num; i++)
{
pthread_create(&pool->threads[i], NULL, threadpool_thread, (void*)pool);
}
pthread_create(&(pool->adjust_tid), NULL, adjust_thread, (void*)pool);/*启动管理者线程*/
return pool;
} while (0);
threadpool_free(pool);
return NULL;
}
-
根据设置的最大线程上线书,为工作线程数组开辟空间,并清零。
-
根据最大任务数,开辟任务队列堆空间。
-
初始化3个互斥锁,2个条件变量。
-
启动初始的工作线程。并设置线程分离
pthread_create(&thread[i],NULL,thread_pool_thread,pool);
-
创建管理者线程:如果任务数大于最小线程池个数,且存活的线程小于允许的最大线程数,增加线程;忙线程小于存活的线程数且存活的线程数大于最小线程数
pthread_cond_signal(&(queue_not_empty)); 通知队列为空,线程可以自行结束 另一种理解:发送任务不为空的假消息,线程执行任务后结束 准确理解:将pthread_cond_wait处释放的锁的条件变量变为真,并重新加锁,打破阻塞
-
设置管理者线程分离,pthread_detach(adjust_tid);
总结
如果从创建实例的接口处调用pthread_create()理解的话,它一共“注册”了两个线程回调:(void*)thread_pool_thread()和(void* * )adjust_thread()
前者主要轮询处理:1、任务队列是否为0的情况:如果为0;加锁阻塞在不为0的条件变量,清除待结束的空闲线程;
2、任务队列不为0的情况,从对头取出任务,更新队头,队长减一操作,激活任务队列不满的条件变量;
3、更新忙状态线程数、
4、执行回调函数任务接口;
5、更新忙状态线程数
后者主要处理
1、加定时;
2、自定义算法:任务数大于最小线程数,且存活的线程数小于最大线程数:
然后新增线程
pthread_create(&(pool->threads[i]), NULL, threadpool_thread, (void*)pool);
更新存活线程数
3、自定义算法:忙线程乘以2小于存活的线程数,且存活的线程数大于最小线程数:
销毁指定的线程数
for (i = 0; i < DEFAULT_THREAD_VARY; i++)
{
/*通知处在空闲状态的线程,他们会自行结束*/
pthread_cond_signal(&pool->queue_not_empty);
}
抽象总结
初始化一个实例,pool,管理一个线程数组,任务节点(接口,自带参数);然后利用线程管理线程;其中比较杂乱难以理解的部分应该是判断在哪个代码行为其添加互斥锁,可能需要对线程活动理解的更为透彻。