内容简介
1.线程池介绍:
当客户端并发请求服务器的时候,服务器的响应是通过创建线程的方式来实现的。但是当并发的请求过于密集的时候,比如同时有十万个客户端请求服务,创建这些线程的开销是不可接受的:按照POSIX标准,一个线程占用8M的空间, 也就是说1G(1024M)的内存只能接受创建128个线程,假设一个服务器的内存是16G,同时也只能够接受创建2048个线程。这对于十万个客户端请求来说,简直是杯水车薪。所以我们需要----线程池。
2.线程池的好处:
- 避免线程太多,使得内存耗尽
- 避免创建线程与销毁线程多产生的代价
- 任务与执行分离
3.线程池原理
客户端请求被加入任务队列(FIFO)等待,线程池通过锁和条件变量通信机制,让任务队列的任务有序进入执行队列,而执行队列创建线程来有序执行这些任务。那么问题来了,何谓有序?
- 先来后到,管理组件负责推送任务
- 一个线程执行一个任务
- 当修改底层数据结构的的时候(访问共享资源),不能发生竞争
基于C的线程池实现
支持层:双向链表构造任务队列和执行队列、定义线程池(管理组件)的数据结构
对于线程池的任务队列、执行队列两个组成部分,实现中都采用了简单的双向链表的底层数据结构。
//任务队列的结构体
struct nTask {
void (*task_func) (void *arg);
void *user_data;
struct nTask *prev;
struct nTack *next;
};
在以上代码的第三行,我们在任务队列中声明一个函数指针,便于需要执行的时候跳转到合适的处理函数。
//执行队列的结构体
struct nWorker {
pthread_t threadid;
int terminate; //终止标识
struct nManager *manager;
struct nWorker *prev;
struct nWorker *next;
};
以上代码中,terminate:终止标识设置为1的时候,意味着需要退出执行循环。
//管理组件的结构体
typedef struct nManager {
struct nTask *tasks;
struct nWorker *workers;
pthread_mutex_t mutex;
pthread_cond_t cond; //条件变量
} TreadPool;
比较有意思的是第6行所定义的互斥锁和第7行所定义的条件变量:这是线程池实现线程同步的关键
- 全局一把互斥锁----访问共享资源的操作需要先取得锁
- 全局唯一条件变量----作用是通知有任务在等待,需要唤醒一个线程
接口层:实现如下接口功能
- 线程池回调:nThreadPoolCallBack
- 线程池创建:nThreadPoolCreate
- 线程池销毁:nThreadPoolDestory
- 推送到任务队列:nThreadPoolPushTask
线程池创建函数:线程池创建一定数量的线程(thread)用于执行(worker), 这里的实现是线程池使用两个for循环分别创建执行队列和对应的线程(用threadid来唯一标识)。在对执行队列进行任何修改之前,都需要上锁防止竞争。
int nThreadPoolCreate(ThreadPool *pool,int numWorkers) {
if (pool == NULL) return -1;
if (numWorkers < 1) numWorkers = 1;
memset(pool, 0, sizeof(ThreadPool));
pthread_cond_t blank_cond = PTHREAD_COND_INITIALIZER;
memcpy(&pool->cond, &blank_cond, sizeof(pthread_cond_t));
pthread_mutex_init(&pool->mutex, NULL);
int i = 0;
for (i = 0;i < numWorkers;i++) {
struct nWorker *worker =(struct nWorker*)malloc(sizeof(struct nWorker));
if (worker == NULL) {
perror("malloc");
return -2;
}
memset(worker, 0, sizeof(struct nWorker));
worker->manager = pool;
int ret = pthread_create(&worker->threadid, NULL, nThreadPoolCallBack, worker);
if (ret) {
perror("pthread_create");
free(worker);
return -3;
}
LIST_INSERT(worker, pool->workers);
}
printf("call_back\n");
return 0;
}
推送到任务队列:一个任务被创建的时候,应该有一个接口函数用于将此任务放到任务队列当中,同样,在使用宏LIST_INSERT修改任务队列的时候需要先加锁保护,防止竞争。重点是,第六行使用了pthread_cond_signal这个函数用于唤醒一个线程,这是实现同步的关键机制。
int nThreadPoolPushTask(ThreadPool *pool, struct nTask *tasks) {
pthread_mutex_lock(&pool->mutex);
LIST_INSERT(tasks, pool->tasks);
pthread_cond_signal(&pool->cond);//唤醒一个等待这个条件的线程
pthread_mutex_unlock(&pool->mutex);
return 0;
}
回调函数:这是线程池中的消费者,是一个while(1)循环。由于涉及LIST_REOMVE删除任务队列中队列的操作,所以需要先获取锁,但是这时我们并不清楚线程目前是否可以被执行,所以在第9行调用了pthread_cond_wait来达成:
- 释放锁,为了不在等待的时候一直占用唯一的互斥锁
- 等待此cond条件变量的线程一旦唤醒,重新获取锁。
将执行的任务从任务队列中删去,while(1)循环在terminate被置位的时候break退出,调用task_func实际执行。
//线程回调函数
static void *nThreadPoolCallBack(void *arg) {
struct nWorker *worker = (struct nWorker*)arg;
while (1) {
pthread_mutex_lock(&worker->manager->mutex);
while (worker->manager->tasks == NULL) {
if (worker->terminate) break;
pthread_cond_wait(&worker->manager->cond, &worker->manager->mutex);
}
//避免死锁
if (worker->terminate) {
pthread_mutex_unlock(&worker->manager->mutex);
break;
}
struct nTask *task = worker->manager->tasks;
LIST_REMOVE(task, worker->manager->tasks);
pthread_mutex_unlock(&worker->manager->mutex);
task->task_func(task);
}
free(worker);
return NULL;
}
业务层:其实所谓的业务就是创建很多线程,测试线程池是否正常运作
void task_entry(struct nTask *task) {
//struct nTask *task = (struct nTask*)task;
int idx = *(int *)task->user_data;
printf("idx:%d\n", idx);
free(task->user_data);
free(task);
}
int main(void) {
ThreadPool pool;
nThreadPoolCreate(&pool,THREADPOOL_INIT_COUNT);
printf("nThreadPoolCreate -- finish\n");
int i = 0;
for (i = 0;i < TASK_INIT_SIZE;i++) {
struct nTask *task = (struct nTask*)malloc(sizeof(struct nTask));
if (task == NULL) {
perror("malloc");
exit(1);
}
memset(task, 0, sizeof(struct nTask));
task->task_func = task_entry;
task->user_data = malloc(sizeof(int));
*(int *)task->user_data = i;
nThreadPoolPushTask(&pool, task);
}
getchar();
}
值得注意的是memset函数的使用是将一块内存区域的内容复制到另一内存当中,但是在这里由于第二个参数为0,所以起到的实际作用是将task所指向结构体的内容全部置为所对应的0值,起到实际上的初始化效果。