线程池是提升多线程程序性能和资源利用率的重要技术。它通过预先创建一定数量的线程,来执行多个任务,避免了频繁创建和销毁线程带来的开销。
本文将带你一步步完成一个简易的C语言线程池实现,适合初学者学习和参考。
线程池设计思路
-
任务队列
线程池内部维护一个任务队列,存放未执行的任务。 -
工作线程
线程池创建多个工作线程,它们不断从任务队列中取任务执行。 -
任务结构
每个任务封装成结构体,包含执行函数和函数参数。 -
同步机制
线程间共享任务队列,需要互斥锁保证数据安全。
工作线程等待任务时,使用条件变量阻塞等待。
用C语言实现线程池 —— 详细代码讲解版
1. 头文件及其作用
#include <stdio.h> // 标准输入输出,printf等
#include <stdlib.h> // 标准库分配内存,malloc/free,exit等
#include <pthread.h> // POSIX线程库,线程、锁、条件变量相关API
#include <unistd.h> // sleep函数,模拟延时使用
-
stdio.h:提供输入输出功能,比如printf打印调试信息。 -
stdlib.h:提供动态内存管理(malloc和free)以及其他工具函数。 -
pthread.h:管理线程、互斥锁、条件变量,是多线程编程基础。 -
unistd.h:提供sleep函数,方便用来模拟任务执行耗时,实际生产环境中可去掉。
2. 线程池所需的基本数据结构
2.1 任务结构体 task_t
typedef struct task_t {
void (*function)(void *); // 任务执行函数指针,接受void*参数,无返回值
void *arg; // 任务函数的参数,使用void*,泛型指针方便传任意数据
struct task_t *next; // 链表指针,方便多个任务串联成队列
} task_t;
-
任务是什么?
任务是线程池要执行的工作,比如打印数字、文件处理等。 -
为什么用函数指针+参数?
使用void (*function)(void *),让多个不同类型的任务用一个接口表示,任务真正逻辑在具体函数中实现。 -
链表的作用
为了维护多个任务,使用链表存储任务队列。
2.2 线程池结构体 threadpool_t
typedef struct threadpool_t {
pthread_mutex_t lock; // 互斥锁,保护任务队列,防止数据竞态
pthread_cond_t notify; // 条件变量,任务队列为空时工作线程等待,添加任务时通知
pthread_t *threads; // 工作线程数组,保存线程ID
task_t *task_queue_head; // 任务队列头指针
task_t *task_queue_tail; // 任务队列尾指针,加任务时方便添加末尾
int thread_count; // 线程池中线程个数
int shutdown; // 标志线程池是否关闭,控制线程循环退出
} threadpool_t;
-
互斥锁
多线程同时操作任务队列必须加锁避免混乱。 -
条件变量
如果任务队列空,工作线程阻塞等待,有新任务时被唤醒。 -
线程数组
线程池创建一定数量线程,用线程数组保存各线程的ID,便于后续pthread_join回收。 -
shutdown标志
用于安全关闭线程池,控制线程退出循环。
3. 线程池创建函数 threadpool_create
threadpool_t *threadpool_create(int thread_count);
-
参数
传入线程数,用来创建多少工作线程。 -
创建流程:
-
分配线程池结构内存。
-
初始化同步原语:互斥锁、条件变量。
-
申请线程数组空间。
-
启动指定数量的线程,线程执行
thread_do_work函数。 -
返回线程池实例指针。
-
-
错误处理
如果分配内存或创建线程失败,要释放已分配资源,防止内存泄漏。
4. 向线程池添加任务 threadpool_add
int threadpool_add(threadpool_t *pool, void (*function)(void *), void *arg);
-
流程:
-
创建任务结构体,分配内存,并设置函数指针和参数。
-
加锁保护任务队列,防止数据冲突。
-
将新任务插入队尾,保持队列 FIFO(先进先出)顺序。
-
发送信号通知等待线程有新任务可执行。
-
解锁,返回0表示成功。
-
-
加锁保护关键
任务队列是共享资源,多个线程同时读取或写入会造成竞态条件和数据破坏。
5. 工作线程执行函数 thread_do_work
void *thread_do_work(void *arg);
-
参数
传入线程池指针,方便访问任务队列及同步对象。 -
执行流程:
-
线程进入无限循环,等待任务。
-
加锁,检查任务队列是否空。
-
队列空则阻塞等待条件变量信号(表示有任务到来)。
-
检查是否需要退出(
shutdown为真)。 -
从队列头获取任务,调整队列指针。
-
解锁,执行任务函数。
-
释放任务结构体内存,继续下一轮循环。
-
-
阻塞等待的意义
避免CPU空忙,线程只有在收到信号时才唤醒执行,有新任务及时响应。
6. 销毁线程池函数 threadpool_destroy
void threadpool_destroy(threadpool_t *pool);
-
作用
关闭线程池,释放所有资源。 -
流程:
-
设置
shutdown标志。 -
广播条件变量,唤醒所有等待线程,让它们退出循环。
-
主线程通过
pthread_join等待所有工作线程安全退出。 -
清理剩余任务(回收任务队列所有任务内存)。
-
销毁锁和条件变量。
-
释放线程数组和线程池结构体内存。
-
-
注意
正确通知并等待线程退出,避免程序异常崩溃。
7. 任务执行示例函数 task_function
void task_function(void *arg);
-
任务例子非常简单:打印线程ID和任务编号,然后模拟耗时1秒。
-
传参时,外部分配一个整型的指针,传入任务。
8. 主函数 main
-
创建4线程线程池。
-
添加10个打印任务。
-
中间用
sleep(5)给线程池时间执行任务(实际项目可用同步手段)。 -
销毁线程池,释放资源。
9. 代码整体结构回顾
|-- 头文件
|-- 任务结构体定义(函数指针+参数+链表指针)
|-- 线程池结构体定义(锁、条件变量、线程、队列指针、标志)
|-- 线程池创建函数(初始化锁、条件变量,创建线程)
|-- 添加任务函数(分配任务结构,加入任务队列,发信号)
|-- 工作线程函数(循环等待任务,执行任务,退出)
|-- 销毁线程池函数(设置退出标志,唤醒线程,等待线程结束,释放资源)
|-- 示例任务函数(打印数字,模拟耗时)
|-- main函数(创建线程池,添加任务,销毁线程池)
完整代码
下面是线程池的完整示例代码,注释中有详细说明。代码简单明了,适合初学者理解。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
// 任务结构体,包含函数指针和参数
typedef struct task_t {
void (*function)(void *);
void *arg;
struct task_t *next;
} task_t;
// 线程池结构体
typedef struct threadpool_t {
pthread_mutex_t lock; // 互斥锁保护任务队列
pthread_cond_t notify; // 条件变量,任务队列非空时唤醒线程
pthread_t *threads; // 工作线程数组
task_t *task_queue_head; // 任务队列头
task_t *task_queue_tail; // 任务队列尾
int thread_count; // 线程个数
int shutdown; // 线程池关闭标志
} threadpool_t;
// 线程池初始化
threadpool_t *threadpool_create(int thread_count);
// 向线程池添加任务
int threadpool_add(threadpool_t *pool, void (*function)(void *), void *arg);
// 线程池销毁
void threadpool_destroy(threadpool_t *pool);
// 工作线程执行函数
void *thread_do_work(void *arg);
// 简单演示任务函数
void task_function(void *arg);
//////////////////// 实现 ////////////////////
threadpool_t *threadpool_create(int thread_count) {
threadpool_t *pool = malloc(sizeof(threadpool_t));
if (!pool) {
perror("malloc threadpool");
return NULL;
}
pool->thread_count = thread_count;
pool->shutdown = 0;
pool->task_queue_head = NULL;
pool->task_queue_tail = NULL;
pool->threads = malloc(sizeof(pthread_t) * thread_count);
if (!pool->threads) {
perror("malloc threads");
free(pool);
return NULL;
}
pthread_mutex_init(&pool->lock, NULL);
pthread_cond_init(&pool->notify, NULL);
// 创建线程
for (int i = 0; i < thread_count; i++) {
if (pthread_create(&pool->threads[i], NULL, thread_do_work, pool) != 0) {
perror("pthread_create");
threadpool_destroy(pool);
return NULL;
}
}
return pool;
}
int threadpool_add(threadpool_t *pool, void (*function)(void *), void *arg) {
if (pool == NULL || function == NULL)
return -1;
task_t *new_task = malloc(sizeof(task_t));
if (!new_task) {
perror("malloc task");
return -1;
}
new_task->function = function;
new_task->arg = arg;
new_task->next = NULL;
pthread_mutex_lock(&pool->lock);
if (pool->task_queue_tail == NULL) {
// 第一个任务
pool->task_queue_head = new_task;
pool->task_queue_tail = new_task;
} else {
// 放置到队尾
pool->task_queue_tail->next = new_task;
pool->task_queue_tail = new_task;
}
// 任务队列非空,通知线程
pthread_cond_signal(&pool->notify);
pthread_mutex_unlock(&pool->lock);
return 0;
}
void threadpool_destroy(threadpool_t *pool) {
if (pool == NULL)
return;
pthread_mutex_lock(&pool->lock);
pool->shutdown = 1;
// 唤醒所有线程
pthread_cond_broadcast(&pool->notify);
pthread_mutex_unlock(&pool->lock);
// 等待线程退出
for (int i = 0; i < pool->thread_count; i++) {
pthread_join(pool->threads[i], NULL);
}
// 清理剩余任务
while (pool->task_queue_head) {
task_t *tmp = pool->task_queue_head;
pool->task_queue_head = pool->task_queue_head->next;
free(tmp);
}
pthread_mutex_destroy(&pool->lock);
pthread_cond_destroy(&pool->notify);
free(pool->threads);
free(pool);
}
void *thread_do_work(void *arg) {
threadpool_t *pool = (threadpool_t *)arg;
while (1) {
pthread_mutex_lock(&pool->lock);
// 等待任务
while (pool->task_queue_head == NULL && !pool->shutdown) {
pthread_cond_wait(&pool->notify, &pool->lock);
}
// 线程池关闭,退出线程
if (pool->shutdown) {
pthread_mutex_unlock(&pool->lock);
pthread_exit(NULL);
}
// 取出任务
task_t *task = pool->task_queue_head;
if (task) {
pool->task_queue_head = task->next;
if (pool->task_queue_head == NULL)
pool->task_queue_tail = NULL;
}
pthread_mutex_unlock(&pool->lock);
// 执行任务
if (task) {
task->function(task->arg);
free(task);
}
}
return NULL;
}
// 简单任务示例:打印数字
void task_function(void *arg) {
int num = *((int *)arg);
printf("Thread %ld is processing task %d\n", pthread_self(), num);
sleep(1); // 模拟任务执行耗时
}
//////////////////// 测试示例 ////////////////////
int main() {
threadpool_t *pool = threadpool_create(4); // 创建4个线程的线程池
if (!pool) {
return -1;
}
// 投递10个任务给线程池
for (int i = 0; i < 10; i++) {
int *num = malloc(sizeof(int));
*num = i + 1;
threadpool_add(pool, task_function, num);
}
// 等待一段时间让所有任务完成
sleep(5);
// 销毁线程池
threadpool_destroy(pool);
return 0;
}
代码运行说明
-
程序创建一个含4个线程的线程池。
-
向线程池添加了10个打印任务。
-
每个任务打印线程ID和任务编号,然后睡眠1秒模拟耗时。
-
主线程等待5秒后销毁线程池。
-
这时线程池中所有任务执行完毕,所有线程也被正确关闭。
总结
希望这篇教程帮你顺利迈出多线程编程的重要一步!如有疑问,欢迎留言交流。
通过这些详细讲解,相信你会更清楚:
-
线程池的核心是任务队列和工作线程。
-
多线程编程必须注意同步,保证数据一致。
-
通过条件变量让无任务的线程睡眠节省CPU资源。
-
通过
shutdown机制,线程池可安全关闭。 -
代码用链表实现任务队列,便于扩展。
尝试自己写写看,调试程序运行,会对多线程和同步有深刻认识。
如需进一步深入,多学习pthread库的原理和用法,以及多线程设计模式。
祝你编程愉快!😊

4178

被折叠的 条评论
为什么被折叠?



