什么是线程池,为什么要使用线程池?
什么是线程池?
首先顾名思义,就是把一堆开辟好的线程放在一个池子统一管理,就是一个线程池。
为什么要使用线程池?
难道来一个请求给它申请一个线程,请求处理完了释放线程不行吗?也行,但是如果创建线程和销毁线程的时间比线程处理请求时间长,而且请求很多的情况下,我们的 CPU 资源都浪费在创建线程和销毁线程上了,所以这种方法效率会比较低。于是,我们可以将若干已经创建好的线程放在一起统一管理,如果来了一个请求,我们就从线程池中取出一个线程来处理请求,处理完了放回池内等待下一个任务,线程池的好处是避免了繁琐的创建和结束线程的时间,有效的利用了 CPU 资源。
还有一个很重要的作用就是
异步解耦
。我们在做开发的时候,日志也是很重要的。对于日志的保存,一般都是保存到磁盘上,要知道磁盘的读写要比内存慢很多倍。如果日志直接写入磁盘的话,会发现整个服务器吞吐量的性能被压在磁盘的读写上面。如何解决性能不被压在磁盘上面,我们引入线程池。线程池起到了一个异步解耦的作用。那什么是异步解耦?就是写磁盘和落盘这个两个动作不在一个流程里面。
线程池的模型
按照我的理解,线程池的作用和双缓冲的作用类似,可以完成任务处理的 ”鱼贯动作“。
最后,如何才能创建一个线程池的模型呢?一般需要以下三个参与者:
- 线程池结构,它负责管理多个线程并提供任务队列的接口
- 工作线程,它们负责处理任务
- 任务队列,存放待处理的任务
有了三个参与者,下一个问题就是怎么使线程池安全有序的工作,可以使用 POSIX 中的信号量、互斥锁和条件变量等同步手段。下面描述了一个客户端发起请求时,服务器端是如何从线程池中取出一个线程进行处理的。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <pthread.h>
#include <assert.h>
// 线程池里所有运行和等待的任务都是一个 Cthread_worker
// 这里的任务列表采用链表数据结构
typedef struct worker
{
// 回调函数,任务运行时会调用此函数
void *(*process)(void *args);
void *args; // 回调函数参数
CThread_worker *next;
}CThread_worker;
// 线程池结构
typedef struct{
pthread_mutex_t queue_lock;
pthread_cond_t queue_ready;
// 链表结构, 线程池中所有等待任务
CThread_worker *queue_head;
int cur_queue_size; // 当前任务队列的任务数
// 是否销毁线程池标志
int shutdown; // 是:1;否:0
pthread_t *threads; // 线程池中的线程
int max_num; // 线程池中允许的最大线程数
}CThread_pool;
// 向线程池中添加任务
int pool_add_worker(void*(*process)(void *), void *arg);
// 线程处理逻辑
void *thread_routine(void *arg);
// 声明一个线程池
static CThread_pool *pool = NULL;
// 初始化线程池
void pool_init(int max_num)
{
int i = 0;
// 为线程池分配空间
pool = (CThread_pool *)malloc(sizeof(CThread_pool));
assert(pool != NULL);
// 初始化互斥量和条件变量
pool->queue_lock = PTHREAD_MUTEX_INITIALIZER;
pool->queue_ready = PTHREAD_COND_INITIALIZER;
// 初始化等待任务链表
pool->queue_head = NULL;
pool->cur_queue_size = 0;
pool->shutdown = 0;
pool->threads = (pthread_t *)malloc(max_num * sizeof(pthread_t));
assert(pool->threads != NULL);
pool->max_num = max_num;
for(; i < max_num; i++)
{
pthread_create(&(pool->threads[i]), NULL, thread_routine, (void *)pool);
}
}
// 向线程中中添加任务
// void *(* process)(void *) :添加任务的处理函数
int pool_add_worker(void *(* process)(void *), void * arg)
{
// 构造一个新任务
CThread_worker *job = (CThread_worker *)malloc(sizeof(CThread_worker));
assert(job != NULL);
job->process = process;
job->args = arg;
job->next = NULL; // 别忘置空
//将任务添加到等待队列中
// 将新任务添加到链表尾
CThread_worker *node = pool->queue_head;
if(!node)
{
while(node)
node = node->next;
node->next = job;
}
else
{
pool->queue_head = job;
}
pool->cur_queue_size++;
pthread_mutex_lock(&(pool->queue_lock));
// 等待队列中有任务了,可以唤醒一个等待线程
// 如果线程池中的线程都处于忙,那么该句不起作用
pthread_cond_signal(&(pool->queue_ready));
return 0;
}
// 工作线程执行函数
void *thread_routine(void * arg)
{
printf("start thread %ld\n", pthread_self());
while(1)
{
// 如果等待队列中没有任务并且不销毁线程池,则线程处于等待状态
// pthread_cond_wait 是一个原子操作,等待前会解锁,唤醒后会加锁
pthread_mutex_lock(&(pool->queue_lock));
while(pool->cur_queue_size == 0 && pool->shutdown == 0)
{
printf("thread %ld is waiting\n", pthread_self());
pthread_cond_wait(&(pool->queue_ready), &(pool->queue_lock));
}
// 如果想要销毁线程池
if(pool->shutdown)
{
// 别忘了先解锁
pthread_mutex_unlock(&(pool->queue_lock));
printf ("thread 0x%x will exit\n", pthread_self ());
pthread_exit(NULL);
}
printf ("thread %ld is starting to work\n", pthread_self ());
assert(pool->cur_queue_size != 0 && pool->shutdown == 0);
assert(pool->queue_head != NULL);
// 等待任务长度减去1,并取走表头元素
pool->cur_queue_size--;
CThread_worker *curJob = pool->queue_head;
if(curJob->next)
pool->queue_head = curJob->next;
else
pool->queue_head = NULL;
pthread_mutex_unlock(&(pool->queue_lock));
// 通过回调函数,执行任务
(*(curJob->prccess))(curJob->args);
free(curJob);
curJob = NULL;
}
// 这一句应该是不可达的
pthread_exit(NULL);
}
// 销毁线程池,等待任务队列中任务不再被执行,但是正在执行的线程会把任务运行完再退出
int pool_destroy()
{
int i = 0;
if(pool->shutdown)
return -1; // 防止两次调用
pool->shutdown = 1;
// 唤醒所有等待线程
pthread_cond_broadcast(&(pool->queue_ready));
// 阻塞线程退出,否则就成为僵尸线程
for(; i < pool->cur_queue_size; i++)
{
pthread_join(pool->threads[i], NULL);
}
free(pool->threads);
// 销毁等待队列
CThread_worker *head = NULL;
while(pool->queue_head)
{
head = pool->queue_head;
pool->queue_head = pool->queue_head->next;
free(head);
}
// 条件变量和互斥量也别忘了销毁
pthread_mutex_destroy(&(pool->queue_lock));
pthread_cond_destroy(&(pool->queue_ready));
free(pool);
// 销毁后置空
pool = NULL;
return 0;
}
【测试代码】
#include "pthread_pool.h"
void *myprocess(void *arg)
{
int *i = (int *)arg;
printf ("threadid is %ld, working on task %d\n", pthread_self (),*i);
sleep (1);/*休息一秒,延长任务的执行时间*/
return NULL;
}
int main()
{
pool_init(5);
// put 10 job
int jobs[10] = {0};
int i;
for(i = 0; i < 10; i++)
{
jobs[i] = i + 1;
pool_add_worker(myprocess, (void *)&jobs[i]);
}
sleep(5);
pool_destroy();
return 0;
}
【运行结果】