线程池设计:线程池实现、任务队列、动态调整线程数

线程池这东西,说白了就是「提前招一批工人,有活就干,没活待命」。

我早年做高并发服务时,每个请求都 new 一个线程,结果系统直接崩了。线程创建销毁的开销,比你想象的大得多。后来我才明白——线程池不是锦上添花,而是刚需。

1. 线程池的核心思想

线程池的本质,是一个「生产者-消费者」模型。

  • 生产者:提交任务的代码(比如你的主线程)
  • 消费者:线程池里的工作线程
  • 缓冲区:任务队列

你想想看,如果没有线程池,每个任务来了就创建线程,任务结束就销毁。高并发下,光线程切换就能把 CPU 吃光。我见过一个项目,高峰期每秒创建 5000 个线程,最后系统直接 OOM。

核心原则:线程的创建和销毁,应该由池子统一管理,而不是交给业务代码。

2. 线程池的三大组件

一个完整的线程池,至少需要三样东西:

  1. 线程数组:存放工作线程的容器
  2. 任务队列:存放待执行任务的缓冲区
  3. 管理组件:负责线程的创建、销毁、调度

我个人习惯把线程池设计成「可配置的」——最小线程数、最大线程数、队列容量,都让调用方自己定。这样灵活得多。

3. 任务队列的实现

任务队列,说白了就是一个「先进先出」的链表或数组。但要注意线程安全。

我建议用 pthread_mutex_t 加锁,配合 pthread_cond_t 做条件变量。为什么?因为工作线程需要「等任务」——没任务时休眠,有任务时唤醒。

// 任务节点
typedef struct task_node {
    void (*func)(void* arg);
    void* arg;
    struct task_node* next;
} task_node_t;

// 任务队列
typedef struct task_queue {
    task_node_t* head;
    task_node_t* tail;
    int count;
    pthread_mutex_t mutex;
    pthread_cond_t cond;
} task_queue_t;

嗯,这里要注意:pthread_cond_wait 必须和 pthread_mutex_lock 配对使用。我曾经见过有人把锁放错位置,结果线程永远醒不过来——那叫一个惨。

4. 线程池的核心结构

typedef struct thread_pool {
    pthread_t* threads;        // 工作线程数组
    int min_threads;           // 最小线程数
    int max_threads;           // 最大线程数
    int current_threads;       // 当前线程数
    int idle_threads;          // 空闲线程数
    int shutdown;              // 关闭标志
    task_queue_t* queue;       // 任务队列
    pthread_mutex_t pool_mutex;
    pthread_cond_t pool_cond;
} thread_pool_t;

这里有个细节:idle_threads 这个字段,很多人会忽略。但动态调整线程数时,它就是关键指标。

5. 工作线程的循环逻辑

每个工作线程,其实就是一个死循环:

void* worker_thread(void* arg) {
    thread_pool_t* pool = (thread_pool_t*)arg;

    while (1) {
        pthread_mutex_lock(&pool->pool_mutex);

        // 队列为空,且池子没关闭,就休眠
        while (pool->queue->count == 0 && !pool->shutdown) {
            pool->idle_threads++;
            pthread_cond_wait(&pool->pool_cond, &pool->pool_mutex);
            pool->idle_threads--;
        }

        if (pool->shutdown) {
            pthread_mutex_unlock(&pool->pool_mutex);
            pthread_exit(NULL);
        }

        // 取任务
        task_node_t* task = dequeue(pool->queue);
        pthread_mutex_unlock(&pool->pool_mutex);

        // 执行任务(注意:这里已经释放了锁)
        task->func(task->arg);
        free(task);
    }
}

你看,逻辑其实不复杂。但有一个坑:执行任务时一定要释放锁。否则其他线程拿不到任务,等于串行执行了。我早期犯过这个错,性能直接打三折。

6. 动态调整线程数

动态调整,说白了就是「根据负载增减线程」。怎么判断负载?看两个指标:

  • 任务队列长度:队列越长,说明处理不过来,需要加线程
  • 空闲线程数:空闲线程太多,说明资源浪费,需要减线程

我常用的策略是:

场景 操作 触发条件
队列长度 > 阈值 增加线程(不超过 max_threads) 每 1 秒检查一次
空闲线程 > 阈值 减少线程(不低于 min_threads) 持续空闲超过 3 秒
void adjust_threads(thread_pool_t* pool) {
    pthread_mutex_lock(&pool->pool_mutex);

    int queue_len = pool->queue->count;
    int idle = pool->idle_threads;

    // 任务积压,加线程
    if (queue_len > 10 && pool->current_threads < pool->max_threads) {
        int add = min(5, pool->max_threads - pool->current_threads);
        for (int i = 0; i < add; i++) {
            pthread_t tid;
            pthread_create(&tid, NULL, worker_thread, pool);
            pool->threads[pool->current_threads++] = tid;
        }
        printf("[调整] 增加 %d 个线程,当前 %d\n", add, pool->current_threads);
    }

    // 空闲太多,减线程(通过设置标志,让线程自己退出)
    if (idle > 5 && pool->current_threads > pool->min_threads) {
        // 这里用条件变量唤醒空闲线程,让它们检查退出标志
        // 具体实现略
    }

    pthread_mutex_unlock(&pool->pool_mutex);
}

嗯,这里要注意:减少线程时,不要直接 pthread_cancel。那样太粗暴,可能导致任务执行一半被中断。我建议用「优雅退出」——设置一个退出标志,让线程执行完当前任务后自己退出。

7. 线程池的关闭

关闭线程池,分两步:

  1. 设置 shutdown = 1,唤醒所有休眠线程
  2. 等待所有线程退出(pthread_join)

我曾经见过有人只做第一步,结果主线程退出了,子线程还在跑——成了孤儿线程。嗯,这很危险。

8. 整体架构图

下面这张图,展示了线程池的核心流程:

主线程 / 生产者 提交任务 任务队列 (FIFO + 互斥锁) 取任务 线程池 线程1 线程2 线程3 ...(动态调整) min ~ max 线程 执行任务 管理组件 动态调整线程数 · 监控队列长度 · 优雅关闭

9. 避坑指南

我曾经踩过的坑:

  • 死锁:在任务执行函数里又去提交任务,导致线程池自己等自己。解决方案:任务里不要直接操作线程池。
  • 线程泄漏:线程退出时没有 detach 或 join,导致资源无法回收。我建议统一用 join,并在关闭时确保所有线程退出。
  • 虚假唤醒:pthread_cond_wait 可能被意外唤醒。所以一定要用 while 循环判断条件,不要用 if。

我的个人建议:

刚开始实现时,先不要搞动态调整。固定线程数跑通,再慢慢加功能。一口吃不成胖子,线程池也一样。

10. 总结

线程池设计,说白了就是三件事:

  • 管好任务队列——加锁、条件变量、FIFO
  • 管好工作线程——创建、销毁、优雅退出
  • 管好动态调整——根据负载增减线程,别拍脑袋

嗯,这些东西说起来简单,但真正写好,需要反复调试。我当年第一个线程池版本,光死锁就修了三天。但一旦跑通,那种成就感,值得。


公众号:蓝海资料掘金营,微信 deep3321