注:本文的任务队列(双向链表结构)使用了utlist.h开源库。
线程池的原理
线程池的原理实际上十分的简单,从线程池结构体中就可以分析出线程池的工作原理。下面是线程池结构体:
typedef struct {
pthread_mutex_t mutex;
pthread_cond_t cond;
task_queue_t *qtask_head; // 任务队列: 双向链表,存放着多个job
pthread_t *thread_arr; // 线程数组
int thread_size; // 线程池中,线程的数量
int exit_flag; // 销毁线程池标志
} thread_poll_t;
可以看到,线程池结构体包含 3种 关键的元素
- 线程数组
线程池是由多个线程组成的,一个池子中必然有多个已经创建好的线程。 - 任务队列
线程池中的线程,处于空闲状态还是工作状态,由任务队列中是否有任务决定。 - 条件变量
控制线程的行为
(1) 在线程回调函数中,如果工作队列中无任务,则线程调用pthread_cond_wait阻塞。
(2) 当调用add_task向线程池中的任务队列添加了job后,调用pthread_cond_signal唤醒阻塞的空闲线程
(3) 线程被唤醒后,将从任务队列中 取出job,执行取出的job。 job->cbk(job->arg);
代码见下
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include "utlist.h"
typedef void(*do_work_cbk)(void *arg);
/* 任务 */
typedef struct {
do_work_cbk cbk;
void *arg;
} job_t;
/* 任务队列:双向链表dblist */
typedef struct task_queue {
job_t *job; // 队列中存放的元素: 任务job
struct task_queue *next, *prev; // 包含了next、prev,就成为双向链表
} task_queue_t;
/* 线程池 */
typedef struct {
pthread_mutex_t mutex;
pthread_cond_t cond;
task_queue_t *qtask_head; // 任务队列: 双向链表,存放着多个job
pthread_t *thread_arr; // 线程数组
int thread_size; // 线程池中,线程的个数
int exit_flag; // 销毁线程池标志
} thread_poll_t;
thread_poll_t *pool = NULL; // 全局变量
/**
* 获取任务队列job的个数
*/
int task_queue_size()
{
task_queue_t *elt;
int count;
DL_COUNT(pool->qtask_head, elt, count);
return count;
}
/**
* 线程回调函数
* @detail 死循环,直到exit_flag标志设为1后,线程才退出
*/
void *thread_cbk(void *arg)
{
task_queue_t *elt;
while (1) {
pthread_mutex_lock(&pool->mutex);
// 任务队列空 && 线程池不销毁
while (task_queue_size(pool->qtask_head) == 0 && pool->exit_flag == 0)
pthread_cond_wait(&pool->cond, &pool->mutex); // 阻塞等待
// 线程池销毁
if (pool->exit_flag == 1) {
pthread_mutex_unlock(&pool->mutex);
return NULL; // 线程退出
}
// 任务队列不空
// 1. 获得任务队列中的第一个job(从任务队列中删除该job)
elt = pool->qtask_head; // 链表头
DL_DELETE(pool->qtask_head, pool->qtask_head); // 头删(任务队列)
pthread_mutex_unlock(&pool->mutex);
// 2. 执行任务
(elt->job->cbk)(elt->job->arg);
// 3. 执行完任务后,释放该job
free(elt->job);
free(elt);
}
}
/**
* 初始化线程池
* @param [in] thread_size 线程池容纳最大线程数
*/
void thread_pool_init(int thread_size)
{
pool = malloc(sizeof(thread_poll_t));
int i;
// 初始化
pthread_cond_init(&pool->cond, NULL);
pthread_mutex_init(&pool->mutex, NULL);
// 创建thread_size个线程
pool->thread_size = thread_size;
pool->thread_arr = malloc(sizeof(pthread_t));
for (i = 0; i < thread_size; i++)
pthread_create(&pool->thread_arr[i], NULL, thread_cbk, NULL);
pool->qtask_head = NULL; // 任务队列初始化 (双向链表初始为空)
pool->exit_flag = 0; // 线程池销毁标志
}
/**
* 释放任务队列中所有的元素
*/
static void task_queue_free(task_queue_t **p_list)
{
task_queue_t *elt, *tmp;
/* now delete each element, use the safe iterator */
DL_FOREACH_SAFE(*p_list, elt, tmp) {
DL_DELETE(*p_list, elt);
free(elt->job); // 记得:先释放elt成员指向的内存
free(elt); // 再释放elt
}
*p_list = NULL;
}
/**
* 销毁线程池
* @detail
* (1) 等待队列中的任务不会再被执行
* (2) 但是正在运行任务的线程会一直把任务运行完后再退出
*/
void thread_pool_destroy(thread_poll_t *pool)
{
if (pool->exit_flag == 1) // 防止多次调用,重复释放
return;
pool->exit_flag = 1; // 销毁标志设为1
// 唤醒所有的线程,使线程看到销毁标志为1后,全部都退出
pthread_cond_broadcast(&(pool->cond));
// 等待线程全部执行完毕
int i;
for (i = 0; i < pool->thread_size; i++)
pthread_join(pool->thread_arr[i], NULL);
free(pool->thread_arr);
// 清空任务队列
task_queue_free(&pool->qtask_head);
pthread_mutex_destroy(&pool->mutex);
pthread_cond_destroy(&pool->cond);
free(pool);
pool = NULL;
}
/**
* 向线程池中,添加任务
* @param [in] pool 线程池
* @param [in] cbk 任务的回调函数
* @param [in] arg 任务的回调函数的参数
* @detail
* 将新任务“尾插”入任务队列
* 唤醒阻塞的空闲线程去处理任务
*/
void add_task(thread_poll_t *pool, do_work_cbk cbk, void *arg)
{
pthread_mutex_lock(&pool->mutex);
// 尾插
// 创建任务
job_t *job = malloc(sizeof(job_t));
job->arg = arg;
job->cbk = cbk;
// 创建task_queue节点 (双链表节点)
task_queue_t *new_node = malloc(sizeof(task_queue_t));
new_node->job = job;
// 将双链表节点,插入到链表
DL_APPEND(pool->qtask_head, new_node);
pthread_mutex_unlock(&pool->mutex);
// 唤醒线程: 注意如果所有线程都在忙碌,这句没有任何作用
pthread_cond_signal(&pool->cond);
}
/* ===================== 测试 ===================== */
// 线程执行的任务回调函数:自定义
void do_work(void *arg)
{
printf("tid[%lu] working...\n", pthread_self() % pool->thread_size);
sleep(1);
}
int main()
{
thread_pool_init(3);
int i;
for (i = 0; i < 10; i++) {
add_task(pool, do_work, NULL);
}
sleep(4);
thread_pool_destroy(pool);
return 0;
}