线程池代码详解

线程池概念

线程池是一种基于池化技术的多线程管理机制。在这种模式下,一组线程被创建并且维护在一个"池"中,这些线程可以被循环利用来执行多个任务。当有新的任务到来时,线程池会尝试使用已经存在的空闲线程,而不是每次都创建新线程。这样做的目的是为了减少因频繁创建和销毁线程所带来的开销。

线程池的优点包括:

  • 提高性能:通过重用现有线程,减少了创建和销毁线程的开销,从而提高了程序的性能。
  • 资源利用率高:线程池可以有效地管理线程资源,确保每个线程都能得到充分利用。
  • 更好的系统稳定性:线程池可以限制系统中线程的最大数量,避免了过多线程导致的内存消耗和上下文切换问题。
  • 提供任务队列管理:线程池通常配备有任务队列,可以对到来的任务进行排队和调度,提高了任务执行的组织性。

线程池的缺点包括:

  • 复杂性增加:相比直接创建线程,线程池的使用和管理更加复杂,需要更多的设计和调试工作。
  • 资源限制:如果线程池中的所有线程都在忙碌,新的任务可能会被迫等待,这可能会导致延迟。
  • 可能的资源泄漏:如果线程池的管理不当,可能会导致资源泄漏,例如,线程没有正确释放或者任务队列过长等。

线程池的主函数

主函数为创建和管理一个简单的线程池,包括初始化线程池、向线程池中添加任务、动态调整线程数量以及销毁线程池。

  • 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_pushpthread_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

posted @ 2024-06-10 09:53  banon  阅读(19)  评论(0编辑  收藏  举报