线程池是预先创建一组线程,并维持一个固定的数量,在需要的时候可以直接提供给程序使用,而不是让程序自行创建和销毁进程,避免了在运行中线程创建和销毁的开销。
1 线程池的构成
生产者线程: 发布任务,将任务放入到消息队列中
队列: 存放了发布的任务,包括任务执行的函数,任务的上下文(参数)
线程池(消费者): 线程池中的线程取出队列中的任务,依次执行
2 线程池的作用
为什么需要线程池?
某些任务特别耗时,严重影响主线程处理其他任务,可以将这些耗时任务放在其他线程异步执行。
耗时任务主要有两类:耗时等待(IO耗时)、耗时处理(CPU耗时)
线程池作用:
-
复用线程资源:线程在使用完后并不是立马销毁,而是仍在线程池中,供下一次使用
-
减少线程创建和销毁的开销:提前创建线程,并在最后统一销毁线程
-
可异步处理生产者线程的任务:对于某个耗时的任务,不要让其在核心线程执行,而是为其分配一个线程执行
-
并发执行:充分利用多核,并发执行任务
3 线程池数量
线程池的数量通常是固定的,因为即使线程池的数量持续增加,由于系统资源是有限的,并不能带来提升,反而由于线程调度带来了更多开销。
固定的线程池数量可以避免频繁创建线程和销毁线程。
按照经验,对于CPU密集型的情况,线程池个数即为CPU核心数,对于IO密集型,则为(线程等待时间 + CPU运算时间)* CPU核心数 / CPU运算时间。
4 应用场景-Nginx
Nginx作为静态服务器时,客户发起请求时,静态资源可直接从Nginx返回。
这些文件一般是存在Nginx本地,因此需要从本地加载进内存中,再返回给客户。
把文件加载进内存的操作不应该由主线程操作,而是使用另外一个线程操作。
流程:
主线程将任务放到一个消息队列中,线程池中的线程取出消息队列中的任务,进行处理,处理完成后放入完成消息队列,提供管道通知主线程,主线程从完成消息队列中取出结果。
5 线程池的实现
5.1 数据结构
任务结构:
typedef struct task_s {
void *next;
handler_pt func; //任务执行的函数
void *arg; //参数
} task_t;
typedef void (*handler_pt)(void * /* ctx */);
任务队列:
typedef struct task_queue_s {
void *head; // 头指针
void **tail; // 尾指针
int block; // 是否阻塞
spinlock_t lock;
pthread_mutex_t mutex; // 互斥锁
pthread_cond_t cond; // 条件变量
} task_queue_t;
尾指针为void **tail
,而任务结构体中第一个是void *next
,tail指向了最后一个任务的next指针,范围为8个字节。
线程池:
struct thrdpool_s {
task_queue_t *task_queue; //任务队列(阻塞队列)
atomic_int quit; // 退出 原子变量
int thrd_count;
pthread_t *threads;
};
5.2 相关函数
5.2.1 创建线程池
创建线程:
static int
__threads_create(thrdpool_t *pool, size_t thrd_count) {
pthread_attr_t attr;
int ret;
ret = pthread_attr_init(&attr); //线程独有的属性
if (ret == 0) {
pool->threads = (pthread_t *)malloc(sizeof(pthread_t) * thrd_count); //分配资源
if (pool->threads) {
int i = 0;
for (; i < thrd_count; i++) {
if (pthread_create(&pool->threads[i], &attr, __thrdpool_worker, pool) != 0) { //创建线程
break;
}
}
pool->thrd_count = i;
pthread_attr_destroy(&attr);
if (i == thrd_count)
return 0;
__threads_terminate(pool); //回滚代码
free(pool->threads);
}
ret = -1;
}
}
__thrdpool_worker函数:
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;
}
销毁线程:
static void
__threads_terminate(thrdpool_t * pool) {
atomic_store(&pool->quit, 1);
__nonblock(pool->task_queue);
int i;
for (i=0; i<pool->thrd_count; i++) {
pthread_join(pool->threads[i], NULL);
}
}
创建任务队列:
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) {
queue->head = NULL;
queue->tail = &queue->head;
queue->block = 1;
return queue;
}
pthread_mutex_destroy(&queue->mutex);
}
free(queue);
}
return NULL;
}
创建线程池:
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;
}
5.2.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;
}
添加任务:
static inline void
add_task(task_queue_t queue, void *task)
{
void **link = (void**)task; //指向新的task的起始地址(8个字节)
*link = NULL; // task->next = NULL
spinlock_lock(&queue->lock);
*queue->tail /* 等价于 queue->tail->next */ = link;
queue->tail = link;
spinlock_unlock(&queue->lock);
pthread_cond_signal(&queue->cond);
}
5.2.3 获取任务
在5.2.1__threads_create中,pthread_create(&pool->threads[i], &attr, __thrdpool_worker, pool
调用了__thrdpool_worker
函数,__thrdpool_worker
执行了__get_task
函数。
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;
}
// pthread_cond_wait流程:
// 1. 先 unlock(&mtx)
// 2. 在 cond 休眠
// --- __add_task 时唤醒
// 3. 在 cond 唤醒
// 4. 加上 lock(&mtx);
pthread_cond_wait(&queue->cond, &queue->mutex);
pthread_mutex_unlock(&queue->mutex);
}
return task;
}
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;
queue->head = *link; // *link为next指针
if (queue->head == NULL) {
queue->tail = &queue->head;
}
spinlock_unlock(&queue->lock);
return task;
}
5.2.4 终止线程
void
thrdpool_terminate(thrdpool_t *pool)
{
atomic_store(&pool->quit, 1);
__nonblock(pool->task_queue);
}
static void
__nonblock(task_queue_t *queue) {
pthread_mutex_lock(&queue->mutex);
queue->block = 0;
pthread_mutex_unlock(&queue->mutex);
pthread_cond_broadcast(&queue->cond);
}