一.线程池介绍
1.介绍:
线程的一种使用模式,线程过多会带来调度开销,进而影响缓存局部性和整体性。而线程池维护着多个线程,等待监督管理者分配可并行执行的任务。这样避免了在短时间内创建和销毁线程的代价。线程池不仅能够内核的充分利用,还能防止过分调度。可用的线程数据取决于可用的并发处理器,处理内核,内存,网络sockets等数量。
2.线程池的作用
(1).复用线程资源;
(2).减少线程创建和销毁的开销
(3).异步处理生产者线程的任务
(4)并发执行:充分利用多核,并发执行核心业务
3.线程池线程数量如何选择
(1)依据:充分利用系统资源
(2)cpu密集型: cpu核心数
(3)io密集型:(线程等待时间+cpu运算时间) * cpu核心数/cpu运算时间
二.线程池实现
1.接口设计 :封装原则 隐藏实现细节,暴露使用接口。用户可调用的三个接口
(1).创建线程池:传入参数为线程池数量
(2).销毁线程池:线程池退出,休眠线程先唤醒后退出,执行任务的线程完成当前任务退出
(3).抛出异步任务的接口:构建队列、放入队列、唤醒一个休眠的线程
2.数据结构设计
(1).线程池数据结构设计
struct thrdpool_s {
task_queue_t *task_queue;//任务队列,阻塞在队列中实现
atomic_int quit;
int thrd_count;
pthread_t *threads;
};
(2).任务事件数据结构设计
typedef struct task_s {
void *next;
handler_pt func;
void *arg;
} task_t;
(3).任务队列数据结构设计
typedef struct task_queue_s {
void *head;
void **tail; //指向最后一个任务的next指针
int block;
spinlock_t lock;
pthread_mutex_t mutex;
pthread_cond_t cond;
} task_queue_t;
三.接口实现:
1.创建和销毁线程池:
使用回滚式编程,在成功后处理完数据逐级销毁资源
thrdpool_t *thrdpool_create(int thrd_count) {
thrdpool_t *pool;
pool = (thrdpool_t*)malloc(sizeof(*pool));
if (pool) {
task_queue_t *queue = __taskqueue_create();
if (queue) {
pool->task_queue = queue;
atomic_init(&pool->quit, 0);
if (__threads_create(pool, thrd_count) == 0)
return pool;
__taskqueue_destroy(queue);
}
free(pool);
}
return NULL;
}
void thrdpool_terminate(thrdpool_t * pool) {
atomic_store(&pool->quit, 1);
__nonblock(pool->task_queue);
}
2.抛异步任务给线程池
int thrdpool_post(thrdpool_t* pool, handler_pt* func, void *arg)
{
if(atomic_load(&pool->quit == 1))
return -1;
task_t *task = (task_t*)malloc(sizeof(task_t));
if(!task)
return -1;
task->func = func;
task->arg = arg;
__add_task(pool->task_queue, task);
return 0;
}
3.创建和销毁任务队列:
(1).创建任务队列:首先从堆上分配一个任务队列的内存,还是使用回滚式编程,分配成功就free,然后进行互斥锁和条件变量的初始化,互斥锁初始化成功就destroy,然后进行条件变量初始化。
static task_queue_t *__taskqueue_create(){
int ret;
task_queue_t *queue = (task_queue_t*)malloc(sizeof(task_queue_t *));
if(queue){
ret = pthread_mutex_init(queue->mutex, NULL);
if(ret == 0){
ret = pthread_cond_init(queue->cond, NULL);
if(ret == 0){
spinlock_init(&queue->lock);
queue->head = NULL;
queue->tail = NULL;
queue->lock = 1;
return queue;
}
pthread_mutex_destroy(&queue->mutex);
}
free(queue);
}
return NULL;
}
(2).销毁任务队列:首先释放队列任务分配的资源,销毁原子锁、条件变量、互斥锁,最后free队列
//销毁任务队列,
static void __taskqueue_destroy(task_queue_t *queue){
task_t *task;
while(task = __pop_task(&queue)){
free(task);
}
spinlock_destroy(&queue->lock);
pthread_cond_destroy(&queue->cond);
pthread_mutex_destroy(&queue->mutex);
free(queue);
}
4.任务队列添加任务和任务出队:
//添加任务
static inline void __add_task(task_queue_t *queue, void *task){
//不限定任务类型, 只要该任务的结构其实内存是一个用于链接下一个节点的指针
void **link = (void**)task;
*link = NULL;
spinlock_lock(&queue->lock);
*queue->tail = link; /*等价于 queue->tail->next * = link*/
queue->tail = link;
spinlock_unlock(&queue->lock);
pthread_cond_signal(&queue->cond);
}
static inline void* __pop_task(task_queue_t *queue){
spinlock_lock(queue->lock);
if(queue->head == NULL)
{
spinlock_unlock(&queue->lock);
return NULL;
}
task_t *task;
task = queue->head;
void **link = (void**)task; //link指向task起始位置
queue->head = *link; //*link指向task->next
if(queue->head == NULL){
queue->tail = &queue->head;
}
spinlock_unlock(queue->lock);
return task;
}
5.获取任务和线程池工作:
static inline void* __get_task(task_queue_t *queue){
task_t *task;
//虚假唤醒
while((task == __pop_task(queue)) == NULL){
pthread_mutex_lock(&queue->mutex);
if(queue->block == 0){
pthread_mutex_unlock(&queue->mutex);
return NULL;
}
//1.先unlock(&mtx)
//2.在cond 休眠
//3.--- __add_task 发送signal唤醒
//3.在cond唤醒
//4.加上lock(%mtx)
pthread_cond_wait(&queue->cond, &queue->mutex);
pthread_mutex_unlock(&queue->mutex);
}
return task;
}
static void *__thrdpool_worker(void *arg){
thrdpool_t *pool = (thrdpool_t*)arg;
task_t *task;
void *ctx;
while(atomic_load(&pool->quit == 0)){
task = (task_t *)__get_task(pool->task_queue);
if(!task) break;
handler_pt func = task->func;
ctx = task->arg;
free(task);
func(ctx);
}
return NULL;
}
问题和总结:
(1).while循环解决虚假唤醒问题:可能被系统唤醒或者signal唤醒,当队列为空再进入循环再次休眠