线程池

线程池

核心概念

  • 本质:由多个“休眠线程”组成的集合,任务到来时唤醒线程执行,任务完成后线程重回休眠,避免频繁创建/销毁线程的资源开销。
  • 核心优势
    1. 复用线程:减少线程创建(内核态切换、栈内存分配)和销毁(资源回收)的系统开销;
    2. 控制并发:避免线程过多导致CPU调度压力增大、内存耗尽;
    3. 提升响应:线程已创建就绪,任务提交后无需等待线程初始化,直接执行。
线程池概念类比图

生活场景类比:

  • 低效方案:来一个客户(任务),招聘1名员工(创建线程),处理完客户后开除员工(销毁线程)——频繁招聘/开除成本高;
  • 高效方案(线程池):招聘一批员工(创建线程池),客户来则唤醒员工(分配任务),处理完客户后员工休息(线程休眠)——复用员工,成本低。

简单的线程池设计

设计逻辑

  1. 创建多个线程 → 2. 线程进入休眠状态(等待条件) → 3. 唤醒线程执行任务 → 4. 任务完成后线程重回休眠

代码实现

#include <stdio.h>
#include <pthread.h>

pthread_cond_t cond;   // 条件变量(控制线程唤醒/休眠)
pthread_mutex_t mutex; // 互斥锁(保护共享逻辑)

// 线程任务函数(休眠-唤醒-执行循环)
void *thread(void *arg) {
    while (1) {
        printf("我线程 %ld 我去休息了\n", pthread_self());
        pthread_cond_wait(&cond, &mutex);  // 休眠,等待被唤醒

        printf("%ld 线程开始工作\n", pthread_self());
    }
}

int main() {
    // 1. 初始化条件变量和互斥锁
    pthread_cond_init(&cond, NULL);
    pthread_mutex_init(&mutex, NULL);

    // 2. 创建线程池(20个线程,tid数组保存线程ID)
    pthread_t tid[20] = {0};
    for (int i = 0; i < 20; i++) {
        pthread_create(&tid[i], NULL, thread, NULL);
    }

    // 3. 主线程:输入任意键唤醒线程
    while (1) {
        printf("输入任意键唤醒线程工作\n");
        getchar();
        pthread_cond_signal(&cond);  // 随机唤醒1个线程
    }
}

线程任务列表设计

设计逻辑

  • 问题:简单线程池无任务存储,唤醒后无明确任务可执行;
  • 解决方案:用链表存储任务(任务列表),线程唤醒后从链表提取任务执行。
线程任务列表设计图

核心:任务节点用结构体封装(含任务函数指针、下一个节点指针),支持“添加任务”(尾插)和“提取任务”(头删)。

代码实现

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>

pthread_cond_t cond;   // 条件变量
pthread_mutex_t mutex; // 互斥锁

// 1. 设计任务节点结构体(封装任务)
struct task_node {
    void (*task)(void);       // 任务函数指针(指向要执行的任务)
    struct task_node *next;   // 指向 next 任务节点
};

struct task_node *head; // 任务链表头节点(全局,方便线程访问)

// 2. 线程任务函数(休眠-唤醒-提取任务-执行)
void *thread(void *arg) {
    while (1) {
        printf("我线程 %ld 我去休息了\n", pthread_self());
        pthread_cond_wait(&cond, &mutex);  // 休眠,唤醒后自动上锁
        printf("%ld 线程开始工作\n", pthread_self());
        pthread_mutex_unlock(&mutex); // 注意:休眠结束后需手动解锁(避免后续操作阻塞)

        // 3. 提取任务(从链表头部移除)
        struct task_node *pos = head;
        if (pos->next == NULL) { // 无任务
            printf("当前无任何任务\n");
            continue;
        }
        pos = pos->next;               // 指向第一个任务
        head->next = pos->next;        // 从链表中删除该任务
        pos->task();                   // 执行任务(回调函数)
        free(pos);                     // 释放任务节点内存
    }
}

// 4. 给线程池添加任务(尾插法插入任务链表)
void add_task(struct task_node *h, void (*task)(void)) {
    struct task_node *pos = h;
    while (pos->next != NULL) { // 找到链表末尾
        pos = pos->next;
    }
    // 创建新任务节点
    struct task_node *xnew = malloc(sizeof(struct task_node));
    xnew->next = NULL;
    xnew->task = task; // 绑定任务函数
    pos->next = xnew;  // 插入链表末尾
}

// 5. 定义具体任务(3个示例任务)
void task_1(void) {
    int i = 0;
    while (1) {
        printf("击杀100只哥布林任务 当前击杀%d只\n", i++);
        if (i == 100) {
            printf("击杀哥布林任务结束\n");
            return;
        }
        sleep(1);
    }
}

void task_2(void) {
    int i = 0;
    while (1) {
        printf("击杀30只史莱姆任务 当前击杀%d只\n", i++);
        if (i == 30) {
            printf("击杀史莱姆结束\n");
            return;
        }
        sleep(1);
    }
}

void task_3(void) {
    int i = 0;
    while (1) {
        printf("挑战黑龙王远征任务 限时一分钟 当前%d秒\n", i++);
        if (i == 60) {
            printf("击杀黑龙王结束\n");
            return;
        }
        sleep(1);
    }
}

int main() {
    // 6. 初始化任务链表头节点
    head = malloc(sizeof(struct task_node));
    head->next = NULL;
    head->task = NULL;

    // 7. 初始化条件变量和互斥锁
    pthread_cond_init(&cond, NULL);
    pthread_mutex_init(&mutex, NULL);

    // 8. 创建线程池(20个线程)
    pthread_t tid[20] = {0};
    for (int i = 0; i < 20; i++) {
        pthread_create(&tid[i], NULL, thread, NULL);
    }

    // 9. 主线程:选择任务并添加到线程池
    while (1) {
        printf("1.击杀哥布林 2.击杀史莱姆 3.击杀黑龙王\n");
        int n = 0;
        scanf("%d", &n);
        // 插入任务
        switch (n) {
            case 1: add_task(head, task_1); break;
            case 2: add_task(head, task_2); break;
            case 3: add_task(head, task_3); break;
        }
        pthread_cond_signal(&cond); // 唤醒线程执行任务
    }
}

官方线程池设计(完整方案)

核心结构体(thread_pool)

  • 封装线程池的所有核心组件,实现“统一管理”:
    typedef struct thread_pool {
        pthread_mutex_t lock;          // 互斥锁(保护任务列表、线程状态)
        pthread_cond_t  cond;          // 条件变量(控制线程唤醒/休眠)
        bool shutdown;                 // 关机标记(true:销毁线程池)
        struct task *task_list;        // 任务链表(存储待执行任务)
        pthread_t *tids;               // 线程ID数组(保存所有活跃线程ID)
        unsigned max_waiting_tasks;    // 最大等待任务数(任务队列容量上限)
        unsigned waiting_tasks;        // 当前等待任务数(任务链表长度)
        unsigned active_threads;       // 活跃线程数(线程池中的线程总数)
    } thread_pool;
    
    // 任务结构体(补充定义,原文档隐含)
    struct task {
        void (*do_task)(void *arg);    // 任务函数(带参数)
        void *arg;                     // 任务函数参数
        struct task *next;             // 指向next任务节点
    };
    

init_pool:初始化线程池

[此处插入“官方线程池结构体初始化图”(锁、条件变量、任务链表、线程数组内存分配),用户自行补充]

  • 作用:初始化线程池的锁、条件变量、任务链表、线程数组,创建指定数量的活跃线程。
// 宏定义(需自行添加,原文档隐含)
#define MAX_ACTIVE_THREADS 20    // 最大活跃线程数
#define MAX_WAITING_TASKS 1000   // 最大等待任务数
#define DEBUG 1                  // 调试模式(打印线程创建信息)

// 初始化线程池结构体 
bool init_pool(thread_pool *pool, unsigned int threads_number) {
    // 1. 初始化互斥锁和条件变量  
    pthread_mutex_init(&pool->lock, NULL);
    pthread_cond_init(&pool->cond, NULL);

    // 2. 初始化核心标记和内存(任务链表、线程数组)
    pool->shutdown = false; // 关机标记:初始为“不关机”
    // 分配任务链表头节点内存
    pool->task_list = malloc(sizeof(struct task)); 
    // 分配线程ID数组内存(存储MAX_ACTIVE_THREADS个线程ID)
    pool->tids = malloc(sizeof(pthread_t) * MAX_ACTIVE_THREADS); 

    // 3. 检查内存分配是否成功
    if (pool->task_list == NULL || pool->tids == NULL) {
        perror("allocate memory error");
        return false;
    }
    pool->task_list->next = NULL; // 任务链表头节点初始化(无任务)

    // 4. 初始化任务数和线程数
    pool->max_waiting_tasks = MAX_WAITING_TASKS; // 最大等待任务数:1000
    pool->waiting_tasks = 0;                     // 当前等待任务数:0
    pool->active_threads = threads_number;       // 活跃线程数:用户指定

    // 5. 循环创建活跃线程
    int i;
    for (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;
        }
        #ifdef DEBUG
        // 调试信息:打印当前线程ID和创建的线程ID
        printf("[%u]:[%s] ==> tids[%d]: [%u] is created.\n",
               (unsigned)pthread_self(), __FUNCTION__,
               i, (unsigned)pool->tids[i]);
        #endif
    }
    return true;
}

routine:线程任务函数(重点、难点)

  • 作用:线程池内所有线程的核心逻辑,实现“等待任务→提取任务→执行任务→处理关机”的循环。
// 线程取消处理函数(避免死锁:线程被取消时释放锁)
void handler(void *arg) {
    pthread_mutex_t *mutex = (pthread_mutex_t *)arg;
    pthread_mutex_unlock(mutex); // 释放互斥锁
    #ifdef DEBUG
    printf("Thread %u canceled, mutex unlocked.\n", (unsigned)pthread_self());
    #endif
}

// 线程的任务函数:所有重点操作都在该函数中
void *routine(void *arg) {
    #ifdef DEBUG
    printf("[%u] is started.\n", (unsigned)pthread_self());  // 打印线程TID 
    #endif

    // 获取线程池结构体地址(条件变量、锁、任务列表均在其中)
    thread_pool *pool = (thread_pool *)arg;
    struct task *p; // 任务指针:指向要提取的任务 

    while (1) {
        // ================================================//
        // 1. 注册线程取消处理函数:防止线程被取消时持有锁导致死锁
        // 参数1:处理函数,参数2:传递给处理函数的参数(锁地址)
        pthread_cleanup_push(handler, (void *)&pool->lock);

        // 2. 上锁:保护任务列表(多个线程同时提取任务,避免竞争)
        pthread_mutex_lock(&pool->lock);
        // ================================================//

        // 3. 无任务且未关机:进入等待(避免空循环)
        while (pool->waiting_tasks == 0 && !pool->shutdown) {
            pthread_cond_wait(&pool->cond, &pool->lock);  
        }

        // 4. 无任务且已关机:退出线程(线程池销毁)
        if (pool->waiting_tasks == 0 && pool->shutdown == true) {
            pthread_mutex_unlock(&pool->lock);
            pthread_exit(NULL); // 不能用break:需退出线程,而非跳出循环
        }

        // 5. 有任务:提取任务(从链表头部移除)
        p = pool->task_list->next;          // 指向第一个任务节点 
        pool->task_list->next = p->next;    // 从链表中删除该任务
        pool->waiting_tasks--;              // 当前等待任务数减1 

        // ================================================//
        // 6. 解锁:任务已提取,后续执行任务无需持有锁
        pthread_mutex_unlock(&pool->lock);  
        pthread_cleanup_pop(0); // 弹出取消处理函数,0表示不执行(已手动解锁)
        // ================================================//

        // 7. 执行任务:关闭线程取消(确保任务执行完,不被中途打断)
        pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);  
        (p->do_task)(p->arg);  // 回调任务函数,传递参数
        pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);  // 恢复取消功能

        // 8. 释放任务节点内存
        free(p); 
    }
    pthread_exit(NULL);
}

add_task:添加任务函数

  • 作用:创建任务节点,插入任务链表,唤醒线程执行任务;若任务数超过上限,放弃任务。
bool add_task(thread_pool *pool, void *(*do_task)(void *arg), void *arg) {
    // 参数说明:
    // pool:线程池结构体地址;do_task:任务函数;arg:任务函数参数

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

    // 2. 初始化任务节点(绑定任务函数和参数)
    new_task->do_task = do_task;
    new_task->arg = arg;
    new_task->next = NULL;

    // 3. 上锁:保护任务链表(避免添加任务时其他线程提取任务)
    // ============= LOCK =============//
    pthread_mutex_lock(&pool->lock);
    // ================================//

    // 4. 检查任务数是否超过上限:超过则放弃任务
    if (pool->waiting_tasks >= MAX_WAITING_TASKS) {
        pthread_mutex_unlock(&pool->lock); // 解锁
        fprintf(stderr, "too many tasks.\n");
        free(new_task); // 释放新任务节点(不执行)
        return false;   // 添加任务失败
    }

    // 5. 尾插法:将新任务插入任务链表末尾
    struct task *tmp = pool->task_list;
    while (tmp->next != NULL) {
        tmp = tmp->next; // 找到链表末尾
    }
    tmp->next = new_task;  // 插入新任务
    pool->waiting_tasks++; // 当前等待任务数加1 

    // 6. 解锁:任务添加完成
    // ============ UNLOCK ============//
    pthread_mutex_unlock(&pool->lock);  
    // ================================//

    #ifdef DEBUG
    // 调试信息:打印添加任务的线程ID和函数名
    printf("[%u][%s] ==> a new task has been added.\n",
           (unsigned)pthread_self(), __FUNCTION__);
    #endif

    // 7. 唤醒线程:通知休眠的线程有新任务
    pthread_cond_signal(&pool->cond); 
    return true;
}

补充:线程池进阶知识

线程池的核心参数(工业级设计)

参数 作用 示例值
核心线程数(core_threads) 线程池长期保留的最小线程数,无任务也不销毁 4
最大线程数(max_threads) 线程池允许的最大线程数,应对任务峰值 20
空闲线程存活时间(keep_alive) 核心线程外的空闲线程,超过该时间未执行任务则销毁 60秒
任务队列容量(queue_capacity) 任务链表的最大长度,超过则触发拒绝策略 1000
拒绝策略(reject_policy) 任务队列满且无空闲线程时的处理方式 丢弃任务/阻塞提交者/抛异常

常见任务拒绝策略

  1. AbortPolicy(默认):抛出异常,提示任务提交失败;
  2. DiscardPolicy:默默丢弃任务,不通知提交者;
  3. DiscardOldestPolicy:丢弃任务队列中最旧的任务(最早提交的),插入新任务;
  4. CallerRunsPolicy:由任务提交者(如主线程)自己执行任务,避免任务丢失。

线程池销毁逻辑

官方设计仅含shutdown标记,完整销毁需三步:

  1. 设置pool->shutdown = true(禁止添加新任务);
  2. 调用pthread_cond_broadcast(&pool->cond)(唤醒所有休眠线程,让线程执行“无任务且关机”逻辑,退出线程);
  3. 循环调用pthread_join(pool->tids[i], NULL)(等待所有线程退出),最后销毁锁、条件变量、释放内存(任务链表、线程数组)。

实践任务实现提示

  1. 目录读取核心函数:
    DIR *dir = opendir("目标目录路径"); // 打开目录
    struct dirent *entry;
    while ((entry = readdir(dir)) != NULL) { // 遍历目录项
        if (entry->d_type == DT_REG) { // DT_REG:普通文件
            // 创建拷贝任务,添加到线程池
        }
    }
    closedir(dir); // 关闭目录
    
  2. 文件拷贝任务函数:
    void copy_file(void *arg) {
        char *file_path = (char *)arg; // 传入文件路径
        // 实现文件拷贝逻辑(打开源文件→创建目标文件→读写数据)
    }
    

下载源代码:
threadpool-demo.zip

posted @ 2025-12-06 08:34  Jaklin  阅读(55)  评论(0)    收藏  举报