解码线程池

线程池核心概念

线程池是管理线程的缓冲机制,提前创建一定数量的线程存入 “池子” 中,任务到来时直接从池中取出线程执行,任务完成后线程不销毁,放回池中待命。其核心作用是减少线程频繁创建 / 销毁的开销,避免线程数量膨胀导致的调度混乱,提升任务并发执行效率,同时降低系统资源消耗。

线程过多会增加 CPU 调度负担,破坏缓存局部性,影响整体性能;频繁创建销毁线程会浪费大量系统资源(如内存、CPU 时间)。线程池通过统一管理线程生命周期、队列化处理任务,让开发者无需关注线程管理细节,仅需专注于任务本身的实现。

image

线程池整体框架

线程池的核心是 “预先创建线程、队列化管理任务”,通过统一管理线程生命周期和任务调度,减少线程频繁创建 / 销毁的开销。

头文件 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>
// 任务队列最大容量:最多允许1000个任务等待执行(避免队列溢出)
#define MAX_WAITING_TASKS	1000  // 线程池最大线程数:最多创建20个活跃线程(避免线程过多导致调度开销)
#define MAX_ACTIVE_THREADS	20

任务结构体 struct task:单个任务的 “容器”

每个任务本质是 “函数 + 参数” 的组合,用链表串联形成任务队列:

struct task
{
    void *(*do_task)(void *arg); // 任务函数指针:指向要执行的具体逻辑(如mytask)
    void *arg;                   // 任务参数:传给do_task的参数(无参数时设为NULL)
    struct task *next;           // 链表指针:将多个task连成单向链表(任务队列)
};

线程池结构体 thread_pool:管理核心资源

线程池的 “总管”,包含线程、任务队列、同步机制和状态标记:

typedef struct thread_pool
{
    pthread_mutex_t lock;		// 互斥锁:保护任务队列、等待任务数等共享资源(防止并发冲突)
    pthread_cond_t  cond;		// 条件变量:实现线程“等待任务”或“被唤醒执行任务”的同步
    bool shutdown;				// 销毁标记:true=线程池需销毁,false=正常运行
    struct task *task_list;		// 任务队列头节点(哨兵节点):不存实际任务,仅用于简化链表操作
    pthread_t *tids;			// 线程ID数组:存储所有活跃线程的ID(用于动态调整线程数)
    unsigned max_waiting_tasks;	// 最大等待任务数(绑定宏MAX_WAITING_TASKS)
    unsigned waiting_tasks;		// 当前等待任务数:实时记录任务队列长度
    unsigned active_threads;	// 当前活跃线程数:实时记录线程池中的线程数量
} thread_pool;

关键设计task_list 是 “哨兵节点”—— 避免操作链表时判断 “头节点是否为空”,简化任务的添加 / 删除逻辑。

接口函数声明:线程池的操作入口

// 初始化线程池:创建初始线程,初始化资源
bool init_pool(thread_pool *pool, unsigned int threads_number); 
// 添加任务:将任务加入队列,唤醒等待线程
bool add_task(thread_pool *pool, void *(*do_task)(void *arg), void *arg); 
// 增加线程:动态扩展线程池(不超过MAX_ACTIVE_THREADS)
int  add_thread(thread_pool *pool, unsigned int additional_threads_number); 
// 减少线程:动态缩减线程池(最少保留1个线程)
int  remove_thread(thread_pool *pool, unsigned int removing_threads_number); 
// 销毁线程池:终止所有线程,释放所有资源
bool destroy_pool(thread_pool *pool); 
// 线程执行逻辑:每个线程启动后循环执行此函数
void *routine(void *arg);

实现文件 thread_pool.c:核心逻辑落地

实现文件是线程池的 “肌肉”,完成头文件声明的所有函数,重点解决 “线程如何循环执行任务”“如何保证同步安全”“如何动态调整线程数” 等问题。

线程核心逻辑 routine:线程的 “一生”

每个线程启动后都会进入此函数的无限循环,直到线程池被销毁。核心是 “等待任务→取任务→执行任务” 的循环,配合同步机制确保安全。

// 线程清理函数:释放互斥锁(配合pthread_cleanup_push使用)
void handler(void *arg)
{
    pthread_mutex_t *lock = (pthread_mutex_t *)arg;
    pthread_mutex_unlock(lock); // 确保线程退出时释放锁
}

void *routine(void *arg)
{
    thread_pool *pool = (thread_pool *)arg; // 将传入的线程池指针强转(arg是init_pool时传的pool)
    struct task *p; // 临时指针:存储取出的任务

    while (1) // 线程循环执行,直到被销毁
    {
        // -------------------------- 注册线程清理函数(防死锁) --------------------------
        // 若线程在持有锁时被取消(如remove_thread调用pthread_cancel),会自动执行handler释放锁
        pthread_cleanup_push(handler, (void *)&pool->lock); 
        // 加锁:操作任务队列前必须获取锁,防止多线程冲突
        pthread_mutex_lock(&pool->lock);

        // -------------------------- 等待任务(无任务且未销毁时休眠) --------------------------
        // 用while而非if:防止“虚假唤醒”(条件变量被唤醒,但任务已被其他线程取走)
        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--; // 等待任务数减1

        // -------------------------- 释放锁+清理函数(准备执行任务) --------------------------
        pthread_mutex_unlock(&pool->lock); // 释放锁:执行任务不需要占用队列锁
        pthread_cleanup_pop(0); // 移除清理函数(0=不执行清理函数,因已解锁)

        // -------------------------- 执行任务(禁用取消,避免任务中断) --------------------------
        pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); // 禁用线程取消(防止任务执行到一半被打断)
        (p->do_task)(p->arg); // 调用任务函数(如mytask)
        pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);  // 恢复取消(允许后续被移除)

        // -------------------------- 释放任务节点(避免内存泄漏) --------------------------
        free(p); 
    }
    pthread_exit(NULL); // 冗余:while(1)不会走到这里,仅为语法完整
}

核心亮点

  • pthread_cleanup_push 防止死锁:线程持有锁时被取消,仍能释放锁;

  • while 循环判断任务状态:解决条件变量 “虚假唤醒” 问题;

  • 执行任务时释放锁:不占用队列锁,提高并发效率;

  • 任务实质上是用户需要交给线程池执行的函数,为了方便线程们执行,一般做法是将函数(即函数指针)及其参数存入一个任务节点,并将节点链成一个链表。

    image

初始化线程池 init_pool:创建 “初始团队”

初始化线程池的核心是 “初始化资源 + 创建初始线程”,为后续接收任务做准备。

bool init_pool(thread_pool *pool, unsigned int threads_number)
{
    // -------------------------- 初始化同步机制(互斥锁+条件变量) --------------------------
    if (pthread_mutex_init(&pool->lock, NULL) != 0) // 初始化互斥锁(默认属性)
    {
        perror("pthread_mutex_init error");
        return false;
    }
    if (pthread_cond_init(&pool->cond, NULL) != 0) // 初始化条件变量(默认属性)
    {
        perror("pthread_cond_init error");
        pthread_mutex_destroy(&pool->lock); // 失败时销毁已初始化的锁
        return false;
    }

    // -------------------------- 初始化线程池状态与内存 --------------------------
    pool->shutdown = false; // 初始为“不销毁”状态
    // 分配任务队列哨兵节点(不存实际任务,仅用于链表操作)
    pool->task_list = malloc(sizeof(struct task));
    // 分配线程ID数组(最多存MAX_ACTIVE_THREADS个线程ID)
    pool->tids = malloc(sizeof(pthread_t) * MAX_ACTIVE_THREADS);
    if (pool->task_list == NULL || pool->tids == NULL)
    {
        perror("allocate memory error");
        // 失败时释放已分配的资源
        free(pool->task_list);
        free(pool->tids);
        pthread_mutex_destroy(&pool->lock);
        pthread_cond_destroy(&pool->cond);
        return false;
    }
    pool->task_list->next = NULL; // 初始化空任务队列(哨兵节点后无任务)

    // -------------------------- 初始化线程池参数 --------------------------
    pool->max_waiting_tasks = MAX_WAITING_TASKS; // 绑定最大等待任务数
    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");
            // 部分线程创建失败:取消已创建的线程,释放资源
            for (int j = 0; j < i; j++)
                pthread_cancel(pool->tids[j]);
            free(pool->task_list);
            free(pool->tids);
            pthread_mutex_destroy(&pool->lock);
            pthread_cond_destroy(&pool->cond);
            return false;
        }
    }
    return true;
}

注意:初始线程数 threads_number 需≤MAX_ACTIVE_THREADS,否则会创建失败。

添加任务 add_task:给线程池 “派活”

将任务加入队列,唤醒等待的线程,核心是 “安全添加任务 + 通知线程”。

bool add_task(thread_pool *pool, void *(*do_task)(void *arg), void *arg)
{
    // -------------------------- 检查线程池状态(已销毁则拒绝任务) --------------------------
    if (pool->shutdown == true)
    {
        fprintf(stderr, "thread pool has been destroyed\n");
        return false;
    }

    // -------------------------- 分配任务节点内存 --------------------------
    struct task *new_task = malloc(sizeof(struct task));
    if (new_task == NULL)
    {
        perror("allocate task memory error");
        return false;
    }

    // -------------------------- 初始化任务节点 --------------------------
    new_task->do_task = do_task; // 绑定任务函数
    new_task->arg = arg;         // 绑定任务参数
    new_task->next = NULL;       // 新任务是链表尾,next设为NULL

    // -------------------------- 加锁操作任务队列 --------------------------
    pthread_mutex_lock(&pool->lock);

    // -------------------------- 检查队列是否满(满则拒绝任务) --------------------------
    if (pool->waiting_tasks >= MAX_WAITING_TASKS)
    {
        pthread_mutex_unlock(&pool->lock);
        fprintf(stderr, "too many tasks (exceed MAX_WAITING_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++; // 等待任务数加1

    // -------------------------- 解锁+唤醒线程 --------------------------
    pthread_mutex_unlock(&pool->lock);
    pthread_cond_signal(&pool->cond); // 唤醒一个等待的线程(有新任务了)

    return true;
}

关键:任务用 “尾插法” 加入队列,保证任务执行顺序与添加顺序一致;唤醒线程用 pthread_cond_signal(仅唤醒一个,避免 “惊群效应”)。

动态调整线程数:add_threadremove_thread

根据任务量动态扩展 / 缩减线程数,平衡 “处理效率” 和 “资源占用”。

  • 增加线程 add_thread

    int add_thread(thread_pool *pool, unsigned additional_threads)
    {
        // 无效请求:新增数量为0,或线程池已销毁
        if (additional_threads == 0 || pool->shutdown == true)
            return 0;
    
        // 计算目标线程数:当前线程数+新增线程数
        unsigned total_threads = pool->active_threads + additional_threads;
        int actual_increment = 0; // 实际新增的线程数(可能小于请求数)
    
        // 循环创建线程:不超过MAX_ACTIVE_THREADS
        for (int i = pool->active_threads; i < total_threads && i < MAX_ACTIVE_THREADS; i++)
        {
            if (pthread_create(&pool->tids[i], NULL, routine, (void *)pool) != 0)
            {
                perror("add threads error");
                if (actual_increment == 0) // 一个都没创建成功,返回失败
                    return -1;
                break; // 部分创建成功,停止创建
            }
            actual_increment++;
        }
    
        // 更新活跃线程数
        pool->active_threads += actual_increment;
        return actual_increment; // 返回实际新增数(供调用者判断)
    }
    
  • 减少线程 remove_thread

    int remove_thread(thread_pool *pool, unsigned int removing_threads)
    {
        // 无效请求:移除数量为0,或线程池已销毁
        if (removing_threads == 0 || pool->shutdown == true)
            return pool->active_threads;
    
        // 计算剩余线程数:最少保留1个(避免线程池无线程可用)
        int remaining_threads = pool->active_threads - removing_threads;
        remaining_threads = remaining_threads > 0 ? remaining_threads : 1;
    
        // 取消多余线程:从后往前取消(后创建的线程先移除,不影响早期线程)
        for (int i = pool->active_threads - 1; i > remaining_threads - 1; i--)
        {
            errno = pthread_cancel(pool->tids[i]); // 发送取消信号给线程
            if (errno != 0) // 取消失败(如线程已退出),停止取消
                break;
        }
    
        // 更新活跃线程数
        pool->active_threads = remaining_threads;
        return remaining_threads; // 返回剩余线程数
    }
    

注意pthread_cancel 会触发线程的 “取消点”(如 pthread_cond_wait),线程会在 routine 中执行清理逻辑后退出,不会直接中断正在执行的任务(因执行任务时禁用了取消)。

销毁线程池 destroy_pool:彻底释放资源

销毁线程池的核心是 “终止所有线程 + 释放所有内存”,确保无资源泄漏。

bool destroy_pool(thread_pool *pool)
{
    // 标记线程池为“销毁状态”
    pool->shutdown = true;
    // 唤醒所有等待的线程:让它们检查shutdown状态并退出
    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("thread [%u] has exited\n", (unsigned)pool->tids[i]);
    }

    // -------------------------- 释放所有内存资源 --------------------------
    free(pool->task_list); // 释放任务队列哨兵节点
    free(pool->tids);      // 释放线程ID数组
    free(pool);            // 释放线程池结构体本身(若pool是动态分配的)

    // -------------------------- 销毁同步对象 --------------------------
    pthread_mutex_destroy(&pool->lock);
    pthread_cond_destroy(&pool->cond);

    return true;
}

关键:用 pthread_cond_broadcast 唤醒所有线程,确保没有线程遗漏;pthread_join 必须在 pthread_cond_broadcast 之后,否则线程可能还在等待,导致 pthread_join 永久阻塞。

主函数 main.c:线程池的实际使用

通过示例演示线程池的完整使用流程:初始化→加任务→动态调整线程→销毁,配合计时线程观察任务执行时间线。

示例任务函数 mytask

模拟实际任务(如数据处理、网络请求),通过休眠指定秒数模拟任务执行耗时:

#include "thread_pool.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
// 示例任务:休眠n秒后打印完成信息(n是传入的参数)
void *mytask(void *arg)
{
    // 注意:实际开发中,void*转int可能有风险(64位系统指针占8字节,int占4字节)
    // 建议用结构体传参,此处为简化示例用int
    int sleep_sec = (int)arg; 
    printf("thread [%u] start task: sleep %d seconds\n", (unsigned)pthread_self(), sleep_sec);
    
    sleep(sleep_sec); // 模拟任务执行耗时
    
    printf("thread [%u] finish task: sleep %d seconds done\n", (unsigned)pthread_self(), sleep_sec);
    return NULL;
}

计时线程 count_time

单独创建一个线程,每秒打印一次时间,方便观察任务执行进度:

// 计时线程:每秒打印一次已过去的时间
void *count_time(void *arg)
{
    int sec = 0;
    while (1)
    {
        sleep(1);
        sec++;
        printf("-------------------------- time passed: %d seconds --------------------------\n", sec);
    }
}

主函数流程

int main(void)
{
    // -------------------------- 创建计时线程(独立于线程池) --------------------------
    pthread_t timer_thread;
    if (pthread_create(&timer_thread, NULL, count_time, NULL) != 0)
    {
        perror("create timer thread error");
        return -1;
    }

    // -------------------------- 初始化线程池(初始2个线程) --------------------------
    thread_pool *pool = malloc(sizeof(thread_pool));
    if (init_pool(pool, 2) == false)
    {
        perror("init thread pool error");
        free(pool);
        return -1;
    }
    printf("init thread pool success: initial threads = %d\n", pool->active_threads);

    // -------------------------- 添加3个任务(随机休眠1-9秒) --------------------------
    printf("\n=== add 3 tasks ===\n");
    for (int i = 0; i < 3; i++)
    {
        int sleep_sec = rand() % 10 + 1; // 1-10秒
        add_task(pool, mytask, (void *)sleep_sec);
    }

    // -------------------------- 查看当前线程数(remove_thread(0)返回当前线程数) --------------------------
    printf("current threads: %d\n", remove_thread(pool, 0));
    sleep(9); // 等待9秒,让前3个任务部分执行

    // -------------------------- 再添加2个任务,动态增加2个线程(共4个线程) --------------------------
    printf("\n=== add 2 more tasks + add 2 threads ===\n");
    for (int i = 0; i < 2; i++)
    {
        int sleep_sec = rand() % 10 + 1;
        add_task(pool, mytask, (void *)sleep_sec);
    }
    int add_num = add_thread(pool, 2);
    printf("add %d threads, current threads: %d\n", add_num, pool->active_threads);

    sleep(5); // 再等待5秒,让新增任务执行

    // -------------------------- 动态减少3个线程(剩余1个线程) --------------------------
    printf("\n=== remove 3 threads ===\n");
    int remain_num = remove_thread(pool, 3);
    printf("remove threads, remaining threads: %d\n", remain_num);

    // -------------------------- 等待所有任务完成,销毁线程池 --------------------------
    sleep(10); // 等待剩余任务执行完成
    printf("\n=== destroy thread pool ===\n");
    destroy_pool(pool);

    // 取消计时线程(避免主函数退出后计时线程仍运行)
    pthread_cancel(timer_thread);
    pthread_join(timer_thread, NULL);

    return 0;
}

示例输出(参考)

init thread pool success: initial threads = 2

=== add 3 tasks ===
thread [1279256128] start task: sleep 4 seconds
thread [1270863424] start task: sleep 7 seconds
current threads: 2
-------------------------- time passed: 1 seconds --------------------------
-------------------------- time passed: 2 seconds --------------------------
-------------------------- time passed: 3 seconds --------------------------
thread [1279256128] finish task: sleep 4 seconds done
thread [1279256128] start task: sleep 8 seconds
-------------------------- time passed: 4 seconds --------------------------
-------------------------- time passed: 5 seconds --------------------------
-------------------------- time passed: 6 seconds --------------------------
thread [1270863424] finish task: sleep 7 seconds done
-------------------------- time passed: 7 seconds --------------------------
-------------------------- time passed: 8 seconds --------------------------

=== add 2 more tasks + add 2 threads ===
thread [1270863424] start task: sleep 6 seconds
thread [1262470720] start task: sleep 4 seconds
add 2 threads, current threads: 4
-------------------------- time passed: 9 seconds --------------------------
-------------------------- time passed: 10 seconds --------------------------
-------------------------- time passed: 11 seconds --------------------------
thread [1279256128] finish task: sleep 8 seconds done
-------------------------- time passed: 12 seconds --------------------------
thread [1262470720] finish task: sleep 4 seconds done
-------------------------- time passed: 13 seconds --------------------------

=== remove 3 threads ===
remove threads, remaining threads: 1
thread [1270863424] finish task: sleep 6 seconds done
-------------------------- time passed: 14 seconds --------------------------
-------------------------- time passed: 15 seconds --------------------------
-------------------------- time passed: 16 seconds --------------------------
-------------------------- time passed: 17 seconds --------------------------
-------------------------- time passed: 18 seconds --------------------------
-------------------------- time passed: 19 seconds --------------------------
-------------------------- time passed: 20 seconds --------------------------
-------------------------- time passed: 21 seconds --------------------------
-------------------------- time passed: 22 seconds --------------------------
-------------------------- time passed: 23 seconds --------------------------

=== destroy thread pool ===
thread [1279256128] has exited

注意事项

  • 避免死锁
    • 互斥锁必须 “成对使用”(加锁后必须解锁,即使执行出错);
    • 线程持有锁时,禁止直接退出(用 pthread_cleanup_push 注册清理函数)。
  • 防止内存泄漏
    • 任务执行完成后,必须 free 任务节点(routine 中的 free(p));
    • 销毁线程池时,必须释放所有动态分配的内存(task_listtidspool)。
  • 任务设计规范
    • 任务函数避免长时间阻塞(会占用线程,影响其他任务执行);
    • 任务参数用结构体传递(避免 void* 强转的风险,如 64 位系统下 intvoid* 长度不匹配)。
  • 同步机制使用
    • pthread_cond_wait 必须在 while 循环中判断条件(防止虚假唤醒);
    • 销毁线程池时,必须先用 pthread_cond_broadcast 唤醒所有线程,再用 pthread_join 回收。
  • 动态调整限制
    • 增加线程时,不超过 MAX_ACTIVE_THREADS
    • 减少线程时,最少保留 1 个线程(避免线程池无可用线程)。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值