线程池代码详解
线程池概念
线程池是一种基于池化技术的多线程管理机制。在这种模式下,一组线程被创建并且维护在一个"池"中,这些线程可以被循环利用来执行多个任务。当有新的任务到来时,线程池会尝试使用已经存在的空闲线程,而不是每次都创建新线程。这样做的目的是为了减少因频繁创建和销毁线程所带来的开销。
线程池的优点包括:
- 提高性能:通过重用现有线程,减少了创建和销毁线程的开销,从而提高了程序的性能。
- 资源利用率高:线程池可以有效地管理线程资源,确保每个线程都能得到充分利用。
- 更好的系统稳定性:线程池可以限制系统中线程的最大数量,避免了过多线程导致的内存消耗和上下文切换问题。
- 提供任务队列管理:线程池通常配备有任务队列,可以对到来的任务进行排队和调度,提高了任务执行的组织性。
线程池的缺点包括:
- 复杂性增加:相比直接创建线程,线程池的使用和管理更加复杂,需要更多的设计和调试工作。
- 资源限制:如果线程池中的所有线程都在忙碌,新的任务可能会被迫等待,这可能会导致延迟。
- 可能的资源泄漏:如果线程池的管理不当,可能会导致资源泄漏,例如,线程没有正确释放或者任务队列过长等。
线程池的主函数
主函数为创建和管理一个简单的线程池,包括初始化线程池、向线程池中添加任务、动态调整线程数量以及销毁线程池。
mytask函数
是一个模拟的工作任务,它会根据传入的参数休眠一定时间来模拟任务执行。count_time函数
是一个计时器任务,它会在一个独立的线程中运行,不断计数。main函数
是程序的入口点,它负责线程池的创建、任务的添加和线程池的销毁。
// 定义一个任务函数,它接受一个void指针作为参数
void *mytask(void *arg)
{
// 将void指针转换为int类型,获取任务执行时间
int n = (int)arg;
// 打印当前线程ID和任务将要执行的时间
printf("[%u][%s] ==> job will be done in %d sec...\n",
(unsigned)pthread_self(), __FUNCTION__, n);
// 使线程休眠n秒,模拟任务执行
sleep(n);
// 打印当前线程ID和任务完成的消息
printf("[%u][%s] ==> job done!\n",
(unsigned)pthread_self(), __FUNCTION__);
// 返回NULL,任务完成
return NULL;
}
// 定义一个计时任务函数,不断计数
void *count_time(void *arg)
{
// 初始化计数器
int i = 0;
// 无限循环
while(1){
// 每秒休眠一次,然后增加计数器
sleep(1);
// 打印经过的秒数
printf("sec: %d\n", ++i);
}
}
// main函数,程序入口点
int main(void)
{
// 定义线程变量a
pthread_t a;
// 创建一个新线程,执行count_time函数
pthread_create(&a, NULL, count_time, NULL);
// 1. 初始化线程池,分配内存并设置初始线程数为2
thread_pool *pool = malloc(sizeof(thread_pool));
init_pool(pool, 2);
// 2. 向线程池中添加3个任务
printf("throwing 3 tasks...\n");
add_task(pool, mytask, (void *)(rand()%10));
add_task(pool, mytask, (void *)(rand()%10));
add_task(pool, mytask, (void *)(rand()%10));
// 3. 检查当前活跃的线程数
printf("current thread number: %d\n",
remove_thread(pool, 0));
// 休眠9秒
sleep(9);
// 4. 向线程池中再添加2个任务
printf("throwing another 2 tasks...\n");
add_task(pool, mytask, (void *)(rand()%10));
add_task(pool, mytask, (void *)(rand()%10));
// 5. 向线程池中添加2个新线程
add_thread(pool, 2);
// 休眠5秒
sleep(5);
// 6. 从线程池中移除3个线程
printf("remove 3 threads from the pool, "
"current thread number: %d\n",
remove_thread(pool, 3));
// 7. 销毁线程池,释放资源
destroy_pool(pool);
// 返回0,程序正常退出
return 0;
}
线程池相关函数
线程初始化——bool init_pool()
**bool init_pool(thread_pool *pool, unsigned int threads_number);**
创建线程池基本结构
- 创建任务结构体包括任务函数指针、传递给任务的参数、 指向下一个任务的指针
- 线程池的管理结构体包含互斥锁、 条件量、线程池销毁、 存储任务的链表、线程池的ID记录、线程池的中线程最大数量、等待状态的线程数量、活跃线程的最大线程数量
//任务结点 单向链表的节点,类型
struct task
{
void *(*do_task)(void *arg); //任务函数指针 指向线程要执行的任务 格式是固定的
void *arg; //需要传递给任务的参数,如果不需要,则NULL
struct task *next; //指向下一个任务结点的指针
};
//线程池的管理结构体
typedef struct thread_pool
{
pthread_mutex_t lock; // 互斥锁
pthread_cond_t cond; // 条件量
bool shutdown; //是否需要销毁线程池
struct task *task_list; //用于存储任务的链表
pthread_t *tids; //用于记录线程池中线程的ID
unsigned max_waiting_tasks; //线程池中线程的数量最大值
unsigned waiting_tasks; //处于等待状态的线程数量
unsigned active_threads; //正在活跃的线程数量
}thread_pool;
线程池初始化函数
函数的主要目的是初始化一个线程池,包括分配内存、初始化互斥锁和条件变量、设置线程池的参数,以及创建指定数量的线程。每个线程将执行routine函数,这个函数通常是处理任务的函数。这个初始化函数在创建线程池时使用,以准备线程池处理任务。
// 初始化线程池
bool init_pool(thread_pool *pool, unsigned int threads_number)
{
// 初始化互斥锁,用于同步对线程池的访问
pthread_mutex_init(&pool->lock, NULL);
// 初始化条件变量,用于线程间的同步
pthread_cond_init(&pool->cond, NULL);
// 设置销毁标志为false,表示线程池不应该被销毁
pool->shutdown = false;
// 为任务链表的头节点分配内存
pool->task_list = malloc(sizeof(struct task));
// 为存储线程ID的数组分配内存,大小为最大活跃线程数
pool->tids = malloc(sizeof(pthread_t) * MAX_ACTIVE_THREADS);
// 如果内存分配失败,打印错误信息并返回false
if(pool->task_list == NULL || pool->tids == NULL){
perror("allocate memory error");
return false;
}
// 初始化任务链表的头节点的next指针为NULL
pool->task_list->next = NULL;
// 设置线程池中最大等待任务数量的限制
pool->max_waiting_tasks = MAX_WAITING_TASKS;
// 初始化等待处理的任务数量为0
pool->waiting_tasks = 0;
// 设置线程池中活跃线程的数量为传入的参数threads_number
pool->active_threads = threads_number;
// 循环创建线程,直到达到活跃线程的数量
for(int i = 0; i < pool->active_threads; i++){
// 创建线程,并将线程ID存储在之前分配的数组中
if(pthread_create(&((pool->tids)[i]), NULL, routine, (void *)pool) != 0){
perror("create threads error");
return false;
}
// 如果定义了DEBUG,打印调试信息
#ifdef DEBUG
printf("[%u]:[%s] ==> tids[%d]: [%u] is created.\n",
(unsigned)pthread_self(), __FUNCTION__,
i, (unsigned)pool->tids[i]);
#endif
}
// 如果所有线程都成功创建,则返回true
return true;
}
增加任务函数——bool add_task()
**bool add_task(thread_pool *pool, void *(*do_task)(void *arg), void *task);**
add_task函数
的作用是将新的任务安全地添加到线程池的任务链表中。
- 首先确保有足够的空间来添加新任务
- 将任务添加到链表的末尾,并更新等待处理的任务数量。使用互斥锁,保证了在多线程环境中对共享资源的安全访问。
- 使用条件变量来通知工作线程有新任务可以处理 ,确保任务能够被有效地分配和执行。
// 定义一个函数,用于向线程池中添加任务
bool add_task(thread_pool *pool, void *(*do_task)(void *arg), void *arg)
{
// 为新任务分配内存
struct task *new_task = malloc(sizeof(struct task));
// 如果内存分配失败,打印错误并返回false
if(new_task == NULL)
{
perror("allocate memory error");
return false;
}
// 设置新任务的函数指针和参数
new_task->do_task = do_task;
new_task->arg = arg;
// 新任务的next指针设置为NULL,表示它是链表的最后一个节点
new_task->next = NULL;
// 加锁,以保护对线程池共享资源的访问
pthread_mutex_lock(&pool->lock);
// 如果等待处理的任务数量已经达到最大值,释放锁,打印错误信息,释放新任务的内存,并返回false
if(pool->waiting_tasks >= MAX_WAITING_TASKS)
{
pthread_mutex_unlock(&pool->lock);
fprintf(stderr, "too many tasks.\n");
free(new_task);
return false;
}
// 从任务链表的头部开始遍历,找到链表的最后一个节点
struct task *tmp = pool->task_list;
while(tmp->next != NULL)
tmp = tmp->next;
// 将新任务添加到任务链表的末尾
tmp->next = new_task;
// 等待处理的任务数量加一
pool->waiting_tasks++;
// 解锁
pthread_mutex_unlock(&pool->lock);
// 如果定义了DEBUG,打印调试信息,表示新任务已经被添加
#ifdef DEBUG
printf("[%u][%s] ==> a new task has been added.\n",
(unsigned)pthread_self(), __FUNCTION__);
#endif
// 发送信号给等待条件变量的线程,其中一个线程将被唤醒来处理新添加的任务
pthread_cond_signal(&pool->cond);
// 返回true,表示任务成功添加
return true;
}
增加线程函数——int add_thread()
**int add_thread(thread_pool *pool, unsigned int additional_threads_number);**
add_thread
函数使线程池能够根据需要动态调整其工作线程的数量,以适应不同的工作负载,作用可以分为以下几点:
- ** 参数检查:**函数首先检查传入的
additional_threads
参数是否为0。如果是0,表示没有新线程需要添加,函数直接返回0。 - 计算总线程数:函数计算在添加新线程后,线程池中总线程的数量。这是通过将当前活跃的线程数与请求添加的线程数相加得到的。
- 线程创建循环:函数进入一个循环,尝试创建新线程,直到达到计算出的总线程数或达到线程池的最大线程限制。
- 线程创建:在循环中,函数使用
pthread_create
尝试创建新线程。新线程将执行routine
函数,并将线程池指针作为参数传递。 - ** 错误处理:**如果在创建新线程的过程中遇到错误,函数会打印错误信息。如果没有成功创建任何线程,函数返回-1表示失败。
- 实际增量记录:对于每个成功创建的线程,函数会增加
actual_increment
计数器的值。 - ** 更新活跃线程数:**循环结束后,函数更新线程池中记录的活跃线程数,增加实际成功创建的线程数。
- 返回值:函数返回实际增加的线程数量,告知调用者实际添加了多少新线程。
// 定义一个函数,用于向线程池中添加指定数量的新线程
int add_thread(thread_pool *pool, unsigned additional_threads)
{
// 如果请求添加的新线程数量为0,则直接返回0,表示没有添加新线程
if(additional_threads == 0)
return 0;
// 计算添加新线程后线程池中总线程的数量
unsigned total_threads = pool->active_threads + additional_threads;
// 定义变量i用于循环,actual_increment用于记录实际增加的线程数量
int i, actual_increment = 0;
// 循环尝试创建新线程,直到达到请求的数量或达到线程池的最大线程限制
for(i = pool->active_threads; i < total_threads && i < MAX_ACTIVE_THREADS; i++){
// 尝试创建新线程,线程执行routine函数,传入的参数是线程池指针
if(pthread_create(&((pool->tids)[i]), NULL, routine, (void *)pool) != 0){
// 如果线程创建失败,打印错误信息
perror("add threads error");
// 如果没有成功创建任何线程,则返回-1表示失败
if(actual_increment == 0)
return -1;
// 如果已经创建了一些线程但未达到请求的数量,跳出循环
break;
}
// 如果线程创建成功,实际增加的线程数量加一
actual_increment++;
// 如果定义了DEBUG,打印调试信息,显示新线程的创建情况
#ifdef DEBUG
printf("[%u]:[%s] ==> tids[%d]: [%u] is created.\n",
(unsigned)pthread_self(), __FUNCTION__,
i, (unsigned)pool->tids[i]);
#endif
}
// 更新线程池中活跃线程的总数
pool->active_threads += actual_increment;
// 返回实际增加的线程数量
return actual_increment;
}
移除线程函数——int remove_thread()
**int remove_thread(thread_pool *pool, unsigned int removing_threads_number);**
remove_thread函数
的作用是:
- 控制线程数量:允许动态减少线程池中的线程数量,以适应减少的工作负载。
- 保持最小线程数:确保线程池中至少保留一个活跃线程,即使请求移除更多线程。
- 安全移除线程:使用pthread_cancel安全地取消线程,而不会影响线程池中的其他线程。
- 错误处理:如果无法取消线程,函数会停止尝试并返回错误码。
- 更新线程池状态:在成功取消线程后,更新线程池的活跃线程计数器。
// 定义一个函数,用于从线程池中移除指定数量的线程
int remove_thread(thread_pool *pool, unsigned int removing_threads)
{
// 如果请求移除的线程数量为0,则直接返回当前活跃线程的数量
if(removing_threads == 0)
return pool->active_threads;
// 计算移除指定数量线程后剩余的线程数量,保证至少有一个线程保持活跃
int remaining_threads = pool->active_threads - removing_threads;
remaining_threads = remaining_threads > 0 ? remaining_threads : 1;
// 从线程池的最后一个线程开始,向前遍历到剩余线程的数量
int i;
for(i = pool->active_threads - 1; i > remaining_threads - 1; i--)
{
// 尝试取消当前遍历到的线程,pthread_cancel的返回值存储在errno中
errno = pthread_cancel(pool->tids[i]);
// 如果取消线程失败,跳出循环
if(errno != 0)
break;
// 如果定义了DEBUG,打印调试信息,显示正在取消的线程ID
#ifdef DEBUG
printf("[%u]:[%s] ==> cancelling tids[%d]: [%u]...\n",
(unsigned)pthread_self(), __FUNCTION__,
i, (unsigned)pool->tids[i]);
#endif
}
// 如果循环结束后i等于活跃线程数减1,表示没有线程被成功取消,返回-1
if(i == pool->active_threads - 1)
return -1;
else
{
// 否则,更新线程池中活跃线程的数量为实际剩余的线程数量
pool->active_threads = i + 1;
// 返回更新后的活跃线程数量
return i + 1;
}
}
注销线程函数——bool destroy_pool()
**bool destroy_pool(thread_pool *pool);**
destroy_pool函数
的作用是:
- 结束线程池的工作:通过设置shutdown标志和广播条件变量,通知所有线程线程池即将关闭。
- 回收线程资源:使用pthread_join等待每个线程结束并回收资源,确保所有线程都被正确结束。
- 释放内存资源:释放线程池中所有动态分配的内存,包括任务链表、线程ID数组和线程池结构体本身。
// 定义一个函数,用于销毁线程池
bool destroy_pool(thread_pool *pool)
{
// 1. 设置线程池的shutdown标志为true,激活所有线程准备关闭
pool->shutdown = true;
// 广播条件变量,通知所有等待的线程线程池将要关闭
pthread_cond_broadcast(&pool->cond);
// 2. 等待所有线程退出
int i;
for(i = 0; i < pool->active_threads; i++)
{
// 等待每个线程结束,pthread_join的返回值存储在errno中
errno = pthread_join(pool->tids[i], NULL);
// 如果等待线程结束失败,打印错误信息
if(errno != 0)
{
printf("join tids[%d] error: %s\n",
i, strerror(errno));
}
// 如果线程成功结束,打印消息表示该线程已经被回收
else
printf("[%u] is joined\n", (unsigned)pool->tids[i]);
}
// 3. 释放线程池占用的内存资源
// 释放任务链表占用的内存
free(pool->task_list);
// 释放存储线程ID的数组占用的内存
free(pool->tids);
// 释放线程池结构体占用的内存
free(pool);
// 返回true,表示线程池已经被成功销毁
return true;
}
线程任务函数——void * routine()
**void *routine(void *arg);**
线程清理函数
handler 函数作为清理函数,确保即使线程在执行过程中被取消,互斥锁也能被正确释放,防止死锁的发生。
// handler函数是一个清理函数,当线程结束时会被调用
void handler(void *arg)
{
// 打印当前线程ID,表示这个线程已经结束
printf("[%u] is ended.\n", (unsigned)pthread_self());
// 释放传入的互斥锁
pthread_mutex_unlock((pthread_mutex_t *)arg);
}
线程任务函数
routine 函数是每个工作线程的主要执行函数。它负责等待任务、获取任务、执行任务,并处理线程的退出。
// routine函数是线程池中每个线程执行的任务函数
void *routine(void *arg)
{
// 如果定义了DEBUG,打印调试信息,表示线程已经启动
#ifdef DEBUG
printf("[%u] is started.\n", (unsigned)pthread_self());
#endif
// 将传入的线程池指针转换为thread_pool类型,并备份
thread_pool *pool = (thread_pool *)arg;
struct task *p;
// 线程会一直运行,直到收到退出信号
while(1)
{
// 设置清理函数handler,确保即使线程在持有互斥锁时被取消,也能正确释放互斥锁
pthread_cleanup_push(handler, (void *)&pool->lock);
pthread_mutex_lock(&pool->lock);
// 如果没有任务且线程池没有关闭,线程就等待新任务
while(pool->waiting_tasks == 0 && !pool->shutdown)
{
pthread_cond_wait(&pool->cond, &pool->lock);
}
// 如果没有任务且线程池正在关闭,线程就退出
if(pool->waiting_tasks == 0 && pool->shutdown == true)
{
pthread_mutex_unlock(&pool->lock);
pthread_exit(NULL); // 不能使用'break'退出循环,否则无法触发自动处理线程函数
}
// 如果有任务,线程就从任务链表中取出一个任务来执行
p = pool->task_list->next;
pool->task_list->next = p->next;
pool->waiting_tasks--;
// 释放互斥锁,并弹出之前设置的清理函数
pthread_mutex_unlock(&pool->lock);
pthread_cleanup_pop(0);
// 设置线程为不可取消状态,执行任务
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
(p->do_task)(p->arg);
// 任务执行完毕后,设置线程为可取消状态
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
// 释放任务结构体占用的内存
free(p);
}
// 线程正常退出
pthread_exit(NULL);
}
辅助清理函数
pthread_cleanup_push
和 pthread_cleanup_pop
是一对函数,它们用于管理线程清理处理程序。当线程因取消请求或 pthread_exit
而终止时,清理处理程序会被执行。这对函数通常成对使用,以确保资源(如互斥锁)被正确释放,防止资源泄漏或死锁。
// 注册清理处理程序
pthread_cleanup_push(handler, (void *)&pool->lock);
// 移除清理处理程序,参数为0表示不立即执行handler函数
pthread_cleanup_pop(0);
pthread_cleanup_push
函数用于注册清理处理程序。它需要两个参数:一个是指向清理处理程序的函数指针,另一个是传递给清理处理程序的参数。在代码中,handler
函数被注册为清理处理程序,而&pool->lock
是传递给它的参数。pthread_cleanup_pop
函数用于移除清理处理程序。它接受一个参数,该参数是一个布尔值。如果这个值为非零,清理处理程序会被立即执行;如果为零,则只是移除清理处理程序而不执行它。多线程编程中,清理处理程序是用来在线程结束时执行一些清理工作的,比如释放资源或解锁互斥锁。pthread_cleanup_pop(0)
的作用是保留清理处理程序在栈中,但不立即执行它,等到线程结束时(如调用pthread_exit)或者线程被取消时,清理处理程序才会被执行。通过pthread_cleanup_push
注册的清理处理程序会在以下情况下自动执行:- 线程调用pthread_exit。
- 线程响应取消请求。
- 当pthread_cleanup_pop的参数非零时。
//向线程池中添加任务
**bool add_task(thread_pool *pool, void *(*do_task)(void *arg), void *task);**
线程池代码源码
// 引入线程池头文件
#include "thread_pool.h"
// 定义一个任务函数,它接受一个void*类型的参数
void *mytask(void *arg)
{
// 将传入的参数转换为int类型
int n = (int)arg;
// 打印当前线程ID和函数名,以及任务将在多少秒内完成
printf("[%u][%s] ==> job will be done in %d sec...\n",
(unsigned)pthread_self(), __FUNCTION__, n);
// 使线程休眠n秒,模拟任务执行
sleep(n);
// 打印当前线程ID和函数名,表示任务完成
printf("[%u][%s] ==> job done!\n",
(unsigned)pthread_self(), __FUNCTION__);
// 返回NULL,任务结束
return NULL;
}
// 定义一个计时函数,它接受一个void*类型的参数
void *count_time(void *arg)
{
// 初始化计时器
int i = 0;
// 无限循环
while(1)
{
// 每秒休眠一次
sleep(1);
// 打印经过的秒数
printf("sec: %d\n", ++i);
}
}
// 主函数
int main(void)
{
// 定义线程ID
pthread_t a;
// 创建一个新线程,运行计时函数
pthread_create(&a, NULL, count_time, NULL);
// 初始化线程池
thread_pool *pool = malloc(sizeof(thread_pool));
init_pool(pool, 2);
// 抛出3个任务
printf("throwing 3 tasks...\n");
add_task(pool, mytask, (void *)(rand()%10));
add_task(pool, mytask, (void *)(rand()%10));
add_task(pool, mytask, (void *)(rand()%10));
// 检查活跃线程的数量
printf("current thread number: %d\n",
remove_thread(pool, 0));
sleep(9);
// 抛出另外2个任务
printf("throwing another 2 tasks...\n");
add_task(pool, mytask, (void *)(rand()%10));
add_task(pool, mytask, (void *)(rand()%10));
// 添加2个线程到线程池
add_thread(pool, 2);
sleep(5);
// 从线程池中移除3个线程
printf("remove 3 threads from the pool, "
"current thread number: %d\n",
remove_thread(pool, 3));
// 销毁线程池
destroy_pool(pool);
// 主函数返回0,程序结束
return 0;
}
// 包含线程池头文件,提供线程池结构和函数声明
#include "thread_pool.h"
// 清理函数,当线程结束时调用
void handler(void *arg)
{
// 打印当前线程ID和结束消息
printf("[%u] is ended.\n", (unsigned)pthread_self());
// 释放互斥锁
pthread_mutex_unlock((pthread_mutex_t *)arg);
}
// 线程执行的任务函数
void *routine(void *arg)
{
// 如果定义了DEBUG,打印线程开始的调试信息
#ifdef DEBUG
printf("[%u] is started.\n", (unsigned)pthread_self());
#endif
// 将传入的线程池指针转换为thread_pool类型
thread_pool *pool = (thread_pool *)arg;
struct task *p;
// 无限循环,直到线程池关闭
while(1)
{
// 设置清理函数,确保即使线程被取消,互斥锁也会被释放
pthread_cleanup_push(handler, (void *)&pool->lock);
// 加锁
pthread_mutex_lock(&pool->lock);
// 如果没有任务且线程池未关闭,等待条件变量
while(pool->waiting_tasks == 0 && !pool->shutdown)
{
pthread_cond_wait(&pool->cond, &pool->lock);
}
// 如果没有任务且线程池正在关闭,解锁并退出线程
if(pool->waiting_tasks == 0 && pool->shutdown == true)
{
pthread_mutex_unlock(&pool->lock);
pthread_exit(NULL);
}
// 如果有任务,从任务链表中取出一个任务
p = pool->task_list->next;
pool->task_list->next = p->next;
// 减少等待任务的数量
pool->waiting_tasks--;
// 解锁
pthread_mutex_unlock(&pool->lock);
// 移除清理函数
pthread_cleanup_pop(0);
// 设置线程为不可取消状态,执行任务
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
(p->do_task)(p->arg);
// 设置线程为可取消状态
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
// 释放任务结构体占用的内存
free(p);
}
// 线程正常退出
pthread_exit(NULL);
}
// 初始化线程池
bool init_pool(thread_pool *pool, unsigned int threads_number)
{
// 初始化互斥锁
pthread_mutex_init(&pool->lock, NULL);
// 初始化条件变量
pthread_cond_init(&pool->cond, NULL);
// 设置线程池关闭标志为false
pool->shutdown = false;
// 为任务链表头节点分配内存
pool->task_list = malloc(sizeof(struct task));
// 为线程ID数组分配内存
pool->tids = malloc(sizeof(pthread_t) * MAX_ACTIVE_THREADS);
// 如果内存分配失败,打印错误并返回false
if(pool->task_list == NULL || pool->tids == NULL)
{
perror("allocate memory error");
return false;
}
// 初始化任务链表头节点的next指针为NULL
pool->task_list->next = NULL;
// 设置线程池中最大等待任务数量
pool->max_waiting_tasks = MAX_WAITING_TASKS;
// 设置当前等待任务数量为0
pool->waiting_tasks = 0;
// 设置当前活跃线程数量
pool->active_threads = threads_number;
// 循环创建线程
for(int i = 0; i < pool->active_threads; i++)
{
// 创建线程,执行routine函数
if(pthread_create(&((pool->tids)[i]), NULL, routine, (void *)pool) != 0)
{
perror("create threads error");
return false;
}
// 如果定义了DEBUG,打印线程创建的调试信息
#ifdef DEBUG
printf("[%u]:[%s] ==> tids[%d]: [%u] is created.\n",
(unsigned)pthread_self(), __FUNCTION__,
i, (unsigned)pool->tids[i]);
#endif
}
// 返回true,表示线程池初始化成功
return true;
}
// 向线程池的任务链表中添加任务的函数
bool add_task(thread_pool *pool, void *(*do_task)(void *arg), void *arg)
{
// 为新任务结构体分配内存
struct task *new_task = malloc(sizeof(struct task));
// 如果内存分配失败,打印错误并返回false
if(new_task == NULL)
{
perror("allocate memory error");
return false;
}
// 设置新任务的执行函数和参数
new_task->do_task = do_task;
new_task->arg = arg;
// 初始化新任务的next指针为NULL,表示它是链表的末尾
new_task->next = NULL;
// 加锁,以保护线程池的共享资源
pthread_mutex_lock(&pool->lock);
// 如果当前等待的任务数量已经达到最大限制,则释放锁和新任务的内存,并返回false
if(pool->waiting_tasks >= MAX_WAITING_TASKS)
{
pthread_mutex_unlock(&pool->lock);
fprintf(stderr, "too many tasks.\n");
free(new_task);
return false;
}
// 找到任务链表的尾部
struct task *tmp = pool->task_list;
while(tmp->next != NULL)
tmp = tmp->next;
// 将新任务添加到任务链表的尾部
tmp->next = new_task;
// 增加线程池中等待的任务数量
pool->waiting_tasks++;
// 解锁
pthread_mutex_unlock(&pool->lock);
// 如果定义了DEBUG,打印调试信息
#ifdef DEBUG
printf("[%u][%s] ==> a new task has been added.\n",
(unsigned)pthread_self(), __FUNCTION__);
#endif
// 发送信号给一个等待的线程,让它开始执行新添加的任务
pthread_cond_signal(&pool->cond);
// 返回true,表示任务成功添加到线程池
return true;
}
// 向线程池中添加新线程的函数
int add_thread(thread_pool *pool, unsigned additional_threads)
{
// 如果没有额外的线程需要添加,则直接返回0
if(additional_threads == 0)
return 0;
// 计算添加新线程后线程池中的总线程数量
unsigned total_threads = pool->active_threads + additional_threads;
// 实际增加的线程数量
int i, actual_increment = 0;
// 循环尝试创建新线程,直到达到计算出的总线程数或达到线程池的最大线程限制
for(i = pool->active_threads; i < total_threads && i < MAX_ACTIVE_THREADS; i++)
{
// 创建新线程,线程执行routine函数
if(pthread_create(&((pool->tids)[i]), NULL, routine, (void *)pool) != 0)
{
perror("add threads error");
// 如果没有线程被创建,返回失败
if(actual_increment == 0)
return -1;
// 如果创建了一些线程但未达到请求的数量,跳出循环
break;
}
// 实际增加的线程数量加一
actual_increment++;
// 如果定义了DEBUG,打印调试信息
#ifdef DEBUG
printf("[%u]:[%s] ==> tids[%d]: [%u] is created.\n",
(unsigned)pthread_self(), __FUNCTION__,
i, (unsigned)pool->tids[i]);
#endif
}
// 更新线程池中活跃线程的总数
pool->active_threads += actual_increment;
// 返回实际增加的线程数量
return actual_increment;
}
// 从线程池中移除指定数量的线程的函数
int remove_thread(thread_pool *pool, unsigned int removing_threads)
{
// 如果没有指定移除的线程数量,则返回当前活跃线程的数量
if(removing_threads == 0)
return pool->active_threads;
// 计算移除指定数量线程后剩余的线程数量,保证至少有一个线程保持活跃
int remaining_threads = pool->active_threads - removing_threads;
remaining_threads = remaining_threads > 0 ? remaining_threads : 1;
// 从线程池的最后一个线程开始,向前遍历到剩余线程的数量
int i;
for(i = pool->active_threads - 1; i > remaining_threads - 1; i--)
{
// 尝试取消当前遍历到的线程
errno = pthread_cancel(pool->tids[i]);
// 如果取消线程失败,跳出循环
if(errno != 0)
break;
// 如果定义了DEBUG,打印调试信息
#ifdef DEBUG
printf("[%u]:[%s] ==> cancelling tids[%d]: [%u]...\n",
(unsigned)pthread_self(), __FUNCTION__,
i, (unsigned)pool->tids[i]);
#endif
}
// 如果循环结束后i等于活跃线程数减1,表示没有线程被成功取消,返回-1
if(i == pool->active_threads - 1)
return -1;
else
{
// 否则,更新线程池中活跃线程的数量为实际剩余的线程数量
pool->active_threads = i + 1;
// 返回更新后的活跃线程数量
return i + 1;
}
}
// 销毁线程池的函数
bool destroy_pool(thread_pool *pool)
{
// 设置线程池的shutdown标志为true,激活所有线程准备关闭
pool->shutdown = true;
// 广播条件变量,通知所有等待的线程线程池将要关闭
pthread_cond_broadcast(&pool->cond);
// 等待所有线程退出
for(int i = 0; i < pool->active_threads; i++)
{
// 等待每个线程结束
errno = pthread_join(pool->tids[i], NULL);
// 如果等待线程结束失败,打印错误信息
if(errno != 0)
{
printf("join tids[%d] error: %s\n", i, strerror(errno));
}
// 如果线程成功结束,打印消息表示该线程已经被回收
else
{
printf("[%u] is joined\n", (unsigned)pool->tids[i]);
}
}
// 释放任务链表占用的内存
free(pool->task_list);
// 释放存储线程ID的数组占用的内存
free(pool->tids);
// 释放线程池结构体占用的内存
free(pool);
// 返回true,表示线程池已经被成功销毁
return true;
}
// 防止头文件重复包含
#ifndef _THREAD_POOL_H_
#define _THREAD_POOL_H_
// 包含所需的头文件
#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <pthread.h>
// 定义最大等待任务数量和最大活跃线程数量的宏
#define MAX_WAITING_TASKS 1000
#define MAX_ACTIVE_THREADS 20
// 定义任务结构体,每个任务包含一个任务函数和参数
struct task
{
void *(*do_task)(void *arg); // 任务函数指针
void *arg; // 任务函数参数
struct task *next; // 指向下一个任务的指针
};
// 定义线程池管理结构体
typedef struct thread_pool
{
pthread_mutex_t lock; // 互斥锁
pthread_cond_t cond; // 条件变量
bool shutdown; // 线程池是否关闭的标志
struct task *task_list; // 任务链表
pthread_t *tids; // 线程ID数组
unsigned max_waiting_tasks; // 最大等待任务数
unsigned waiting_tasks; // 当前等待任务数
unsigned active_threads; // 当前活跃线程数
} thread_pool;
// 声明线程池的初始化函数
bool init_pool(thread_pool *pool, unsigned int threads_number);
// 声明添加任务到线程池的函数
bool add_task(thread_pool *pool, void *(*do_task)(void *arg), void *task);
// 声明向线程池中添加线程的函数
int add_thread(thread_pool *pool, unsigned int additional_threads_number);
// 声明从线程池中删除线程的函数
int remove_thread(thread_pool *pool, unsigned int removing_threads_number);
// 声明销毁线程池的函数
bool destroy_pool(thread_pool *pool);
// 声明线程池中线程执行的任务函数
void *routine(void *arg);
// 结束头文件的条件编译
#endif