线程池
线程池
核心概念
- 本质:由多个“休眠线程”组成的集合,任务到来时唤醒线程执行,任务完成后线程重回休眠,避免频繁创建/销毁线程的资源开销。
- 核心优势:
- 复用线程:减少线程创建(内核态切换、栈内存分配)和销毁(资源回收)的系统开销;
- 控制并发:避免线程过多导致CPU调度压力增大、内存耗尽;
- 提升响应:线程已创建就绪,任务提交后无需等待线程初始化,直接执行。
生活场景类比:
- 低效方案:来一个客户(任务),招聘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) | 任务队列满且无空闲线程时的处理方式 | 丢弃任务/阻塞提交者/抛异常 |
常见任务拒绝策略
- AbortPolicy(默认):抛出异常,提示任务提交失败;
- DiscardPolicy:默默丢弃任务,不通知提交者;
- DiscardOldestPolicy:丢弃任务队列中最旧的任务(最早提交的),插入新任务;
- CallerRunsPolicy:由任务提交者(如主线程)自己执行任务,避免任务丢失。
线程池销毁逻辑
官方设计仅含shutdown标记,完整销毁需三步:
- 设置
pool->shutdown = true(禁止添加新任务); - 调用
pthread_cond_broadcast(&pool->cond)(唤醒所有休眠线程,让线程执行“无任务且关机”逻辑,退出线程); - 循环调用
pthread_join(pool->tids[i], NULL)(等待所有线程退出),最后销毁锁、条件变量、释放内存(任务链表、线程数组)。
实践任务实现提示
- 目录读取核心函数:
DIR *dir = opendir("目标目录路径"); // 打开目录 struct dirent *entry; while ((entry = readdir(dir)) != NULL) { // 遍历目录项 if (entry->d_type == DT_REG) { // DT_REG:普通文件 // 创建拷贝任务,添加到线程池 } } closedir(dir); // 关闭目录 - 文件拷贝任务函数:
void copy_file(void *arg) { char *file_path = (char *)arg; // 传入文件路径 // 实现文件拷贝逻辑(打开源文件→创建目标文件→读写数据) }
下载源代码:
threadpool-demo.zip

浙公网安备 33010602011771号