博客新地址:https://github.com/AngryHacker/articles/blob/master/src/code_reading/threadpool.md
线程池介绍
线程池可以说是项目中经常会用到的组件,在这里假设读者都有一定的多线程基础,如果没有的话不妨在这里进行了解:POSIX 多线程基础。
线程池是什么?我的简单理解是有一组预先派生的线程,然后有一个管理员来管理和调度这些线程,你只需不断把需要完成的任务交给他,他就会调度线程的资源来帮你完成。
那么管理员是怎么做的呢?一种简单的方式就是,管理员管理一个任务的队列,如果收到新的任务,就把任务加到队列尾。每个线程盯着队列,如果队列非空,就去队列头拿一个任务来处理(每个任务只能被一个线程拿到),处理完了就继续去队列取任务。如果没有任务了,线程就休眠,直到任务队列不为空。如果这个管理员更聪明一点,他可能会在没有任务或任务少的时候减少线程的数量,任务处理不过来的时候增加线程的数量,这样就实现了资源的动态管理。
那么任务是什么呢?以后台服务器为例,每一个用户的请求就是一个任务,线程不断的在请求队列里取出请求,完成后继续处理下一个请求。
简单图示为:
线程池有一个好处就是减少线程创建和销毁的时间,在任务处理时间比较短的时候这个好处非常显著,可以提升任务处理的效率。
线程池实现
这里介绍的是线程池的一个简单实现,在创建的时候预先派生指定数量的线程,然后去任务队列取添加进来的任务进行处理就好。
作者说之后会添加更多特性,我们作为学习之后就以这个版本为准就好了。
项目主页:threadpool
数据结构
主要有两个自定义的数据结构
threadpool_task_t
用于保存一个等待执行的任务。一个任务需要指明:要运行的对应函数及函数的参数。所以这里的 struct 里有函数指针和 void 指针。
typedef struct {
void (*function)(void *);
void *argument;
} threadpool_task_t;
thread_pool_t
一个线程池的结构。因为是 C 语言,所以这里任务队列是用数组,并维护队列头和队列尾来实现。
struct threadpool_t {
pthread_mutex_t lock; /* 互斥锁 */
pthread_cond_t notify; /* 条件变量 */
pthread_t *threads; /* 线程数组的起始指针 */
threadpool_task_t *queue; /* 任务队列数组的起始指针 */
int thread_count; /* 线程数量 */
int queue_size; /* 任务队列长度 */
int head; /* 当前任务队列头 */
int tail; /* 当前任务队列尾 */
int count; /* 当前待运行的任务数 */
int shutdown; /* 线程池当前状态是否关闭 */
int started; /* 正在运行的线程数 */
};
函数
对外接口
threadpool_t *threadpool_create(int thread_count, int queue_size, int flags);
创建线程池,用 thread_count 指定派生线程数,queue_size 指定任务队列长度,flags 为保留参数,未使用。int threadpool_add(threadpool_t *pool, void (*routine)(void *),void *arg, int flags);
添加需要执行的任务。第二个参数为对应函数指针,第三个为对应函数参数。flags 未使用。int threadpool_destroy(threadpool_t *pool, int flags);
销毁存在的线程池。flags 可以指定是立刻结束还是平和结束。立刻结束指不管任务队列是否为空,立刻结束。平和结束指等待任务队列的任务全部执行完后再结束,在这个过程中不可以添加新的任务。
内部辅助函数
static void *threadpool_thread(void *threadpool);
线程池每个线程所执行的函数。int threadpool_free(threadpool_t *pool);
释放线程池所申请的内存资源。
线程池使用
编译
参考项目根目录下的 Makefile, 直接用 make
编译。
测试用例
项目提供了三个测试用例(见 threadpool/test/
),我们可以以此来学习线程池的用法并测试是否正常工作。这里提供其中一个:
#define THREAD 32
#define QUEUE 256
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <assert.h>
#include "threadpool.h"
int tasks = 0, done = 0;
pthread_mutex_t lock;
void dummy_task(void *arg) {
usleep(10000);
pthread_mutex_lock(&lock);
/* 记录成功完成的任务数 */
done++;
pthread_mutex_unlock(&lock);
}
int main(int argc, char **argv)
{
threadpool_t *pool;
/* 初始化互斥锁 */
pthread_mutex_init(&lock, NULL);
/* 断言线程池创建成功 */
assert((pool = threadpool_create(THREAD, QUEUE, 0)) != NULL);
fprintf(stderr, "Pool started with %d threads and "
"queue size of %d\n", THREAD, QUEUE);
/* 只要任务队列还没满,就一直添加 */
while(threadpool_add(pool, &dummy_task, NULL, 0) == 0) {
pthread_mutex_lock(&lock);
tasks++;
pthread_mutex_unlock(&lock);
}
fprintf(stderr, "Added %d tasks\n", tasks);
/* 不断检查任务数是否完成一半以上,没有则继续休眠 */
while((tasks / 2) > done) {
usleep(10000);
}
/* 这时候销毁线程池,0 代表 immediate_shutdown */
assert(threadpool_destroy(pool, 0) == 0);
fprintf(stderr, "Did %d tasks\n"