线程池的引入是因为避免线程太多 导致内存耗尽 避免创建线程与销毁线程的代价(磁盘操作比内存操作要缓慢很多,创建线程时会导致线程挂起)任务和执行分离 线程池一般是作为一个组件 SDK进行封装
线程池的组成
1.任务队列
2.执行队列
3.管理组件 ---->锁 使得任务和执行有序
线程池的定义代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
//注:在本代码调试中LIST_INSERT编译会出现报错 原因是 do 前 未加; 但实际上 加了执行代码会报错 编译不会报错而已
#define LIST_INSERT(item, list)do {item->prev = NULL;item->next = list;if ((list) != NULL) (list)->prev = item;(list) = item;} while(0);
#define LIST_REMOVE(item, list) do {if (item->prev != NULL) item->prev->next = item->next;if (item->next != NULL) item->next->prev = item->prev;if (list == item) list = item->next;item->prev = item->next = NULL;} while(0);
//定义线程池的结构体
//定义任务队列
struct nTask
{
//用来存储任务
void (*task_func)(struct nTask* task); //任务
void* user_data; //用来执行任务的参数
//节点的定义
struct nTask* prev;
struct nTask* next;
};
//定义执行队列
struct nWorker
{
pthread_t threadid; //线程id
struct nManager* manager; //以防出现意外需要更改任务队列
int stoptask; //任务终止标识 代表任务终止退出
//节点的定义
struct nWorker* prev;
struct nWorker* next;
};
//管理组件用来管理上面的任务和执行队列 也就是线程池
typedef struct nManager
{
struct nTask* task; //任务是外界传入的因此不必初始化
struct nWorker* worker; //线程数是需要初始化
pthread_mutex_t mutex; //锁初始化
//条件等待变量
pthread_cond_t cond; //变量初始化
}ThreadPool;
//接口定义
//线程的回调函数
//回调函数不等于任务 callback != task
static void *nThreadPoolCallback(void* arg)
{
struct nWorker* worker = (struct nWorker*)arg;
//线程池会一直工作
while (1)
{
pthread_mutex_lock(&worker->manager->mutex);
while (worker->manager->task == NULL)//判断是否有任务 没有就不执行
{
if (worker->stoptask) break; //当标识为1的时候代表任务终止 直接跳出循环
pthread_cond_wait(&worker->manager->cond, &worker->manager->mutex); //条件等待 只要没有任务就在等待中
}
if (worker->stoptask)
{
pthread_mutex_unlock(&worker->manager->mutex);
break;
}
//若上面任务不为空
struct nTask* task = worker->manager->task; //从任务队列中取出第一个 首节点
//取出第一个任务之后将此任务从队列中移除 保证了 任务队列的第一个始终是最新的
LIST_REMOVE(task, worker->manager->task);
pthread_mutex_unlock(&worker->manager->mutex);
//执行任务中的方法
task->task_func(task);
}
free(worker);
}
//这三个都是接口提供给外界使用的
//创建线程池和初始化线程池
int nThreadPoolinit(ThreadPool *pool,int nworkers)
{
if (pool == NULL) return -1; //如果线程数为空则直接返回
if (nworkers < 1) nworkers = 1; //如果线程数小于1则赋予默认最小值1
//定义一把空白的锁
pthread_cond_t blank_cond = PTHREAD_COND_INITIALIZER;
//初始化
memcpy(&pool->cond,&blank_cond,sizeof(pthread_cond_t));
//mutex初始化
pthread_mutex_init(&pool->mutex,NULL);
int i = 0;
for (i = 0;i<nworkers;i++)
{
//创建nworker
struct nWorker *worker = (struct nWorker*)malloc(sizeof(struct nWorker));
//创建失败打印消息
if (worker == NULL)
{
perror("malloc");
return -2;
};
//创建成功加入到队列中
memset(worker,0,sizeof(struct nWorker));
worker->manager = pool; //用来操作
//创建线程任务
int ref = pthread_create(&worker->threadid,NULL, *nThreadPoolCallback, worker); //创建成功返回非0 失败返回0
if (ref)
{
perror("pthread_create");
free(worker);
return -3;
}
//添加到队列
LIST_INSERT(worker,pool->worker);
}
//初始化完成返回 0
return 0;
}
//销毁线程池
int nThreadPoolDestory(ThreadPool* pool, int nworkers )
{
struct nWorker* worker = NULL;
for (worker = pool->worker ; worker !=NULL ;worker = worker->next)
{
worker->stoptask = 1; //将任务终止标识置1
}
pthread_mutex_lock(&pool->mutex); //这里的锁和上面回调函数中等待时加的锁是一把锁所以不会出现死锁的情况
pthread_cond_broadcast(&pool->cond); //条件广播 //唤醒所有线程
pthread_mutex_unlock(&pool->mutex);
//销毁
pool->task = NULL;
pool->worker = NULL;
}
//添加任务
int nThreadPoolPushTask(ThreadPool* pool, struct nTask *task)
{
pthread_mutex_lock(&pool->mutex)
LIST_INSERT(task,pool->task); //把当前的任务添加到任务队列中
pthread_cond_signal(&pool->cond);//通知线程 使得线程唤醒 唤醒一个
pthread_mutex_unlock(&pool->mutex);
}
//
//sdk -->debug thread pool
//测试模块
#if 1
#define TASK_INIT_SIZE 20
#define Task_Init_Size 1000
//任务入口
void task_entry(struct nTask* task)
{
int index = *(int*)task->user_data;
printf("index: %d\n", index);
//用完要释放防止内存泄露
free(task->user_data);
free(task);
}
int main(void)
{
ThreadPool pool;
nThreadPoolinit(&pool, TASK_INIT_SIZE);
int i = 0;
//往线程池中丢任务
for ( i = 0; i < TASK_INIT_SIZE; i++)
{
struct nTask* task = (struct nTask*)malloc(sizeof(struct nTask));
if (task == NULL)
{
perror("malloc task");
exit(1);
}
memset(task, 0, sizeof(struct nTask));
task->task_func = task_entry;
task->user_data = malloc(sizeof(int)); //初始化分配内存空间
*(int*)task->user_data = i;
//这行代码的意思是将变量 i 赋值给指向 task->user_data 的整型指针。\
需要注意的是,由于 task->user_data 是一个 void 指针,需要进行强制类型转换为整型指针才能对其进行赋值操作。\
*(int*)是强制转换成整型指针
nThreadPoolPushTask(&pool, task);
}
getchar();
}
#endif
正确通过编译执行的线程池SDK组件的代码如下:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#define LIST_INSERT(item, list)do {item->prev = NULL;item->next = list;if ((list) != NULL) (list)->prev = item;(list) = item;} while(0);
#define LIST_REMOVE(item, list) do {if (item->prev != NULL) item->prev->next = item->next;if (item->next != NULL) item->next->prev = item->prev;if (list == item) list = item->next;item->prev = item->next = NULL;} while(0);
struct nTask {
void (*task_func)(struct nTask *task);
void* user_data;
struct nTask *prev;
struct nTask *next;
};
struct nWorker {
pthread_t threadid;
int terminate;
struct nManager *manager;
struct nWorker *prev;
struct nWorker *next;
};
typedef struct nManager {
struct nTask *tasks;
struct nWorker *workers;
pthread_mutex_t mutex;
pthread_cond_t cond;
} ThreadPool;
// callback != task
static void *nThreadPoolCallback(void *arg) {
struct nWorker *worker = (struct nWorker*)arg;
while (1) {
pthread_mutex_lock(&worker->manager->mutex);
while (worker->manager->tasks == NULL) {
if (worker->terminate) break;
pthread_cond_wait(&worker->manager->cond, &worker->manager->mutex);
}
if (worker->terminate) {
pthread_mutex_unlock(&worker->manager->mutex);
break;
}
struct nTask *task = worker->manager->tasks;
LIST_REMOVE(task, worker->manager->tasks);
pthread_mutex_unlock(&worker->manager->mutex);
task->task_func(task);
}
free(worker);
}
// API
int nThreadPoolCreate(ThreadPool *pool, int numWorkers) {
if (pool == NULL) return -1;
if (numWorkers < 1) numWorkers = 1;
memset(pool, 0, sizeof(ThreadPool));
pthread_cond_t blank_cond = PTHREAD_COND_INITIALIZER;
memcpy(&pool->cond, &blank_cond, sizeof(pthread_cond_t));
//pthread_mutex_init(&pool->mutex, NULL);
pthread_mutex_t blank_mutex = PTHREAD_MUTEX_INITIALIZER;
memcpy(&pool->mutex, &blank_mutex, sizeof(pthread_mutex_t));
int i = 0;
for (i = 0; i < numWorkers; i++) {
struct nWorker* worker = (struct nWorker*)malloc(sizeof(struct nWorker));
if (worker == NULL) {
perror("malloc");
return -2;
}
memset(worker, 0, sizeof(struct nWorker));
worker->manager = pool; //
int ret = pthread_create(&worker->threadid, NULL, nThreadPoolCallback, worker);
if (ret) {
perror("pthread_create");
free(worker);
return -3;
}
LIST_INSERT(worker, pool->workers);
}
// success
return 0;
}
// API
int nThreadPoolDestory(ThreadPool *pool, int nWorker) {
struct nWorker *worker = NULL;
for (worker = pool->workers; worker != NULL; worker = worker->next) {
worker->terminate;
};
pthread_mutex_lock(&pool->mutex);
pthread_cond_broadcast(&pool->cond);
pthread_mutex_unlock(&pool->mutex);
pool->workers = NULL;
pool->tasks = NULL;
return 0;
}
// API
int nThreadPoolPushTask(ThreadPool *pool, struct nTask *task) {
pthread_mutex_lock(&pool->mutex);
LIST_INSERT(task, pool->tasks);
pthread_cond_signal(&pool->cond);
pthread_mutex_unlock(&pool->mutex);
}
//
// sdk --> debug thread pool
#if 1
#define THREADPOOL_INIT_COUNT 20
#define TASK_INIT_SIZE 1000
void task_entry(struct nTask *task) { //type
//struct nTask *task = (struct nTask*)task;
int idx = *(int *)task->user_data;
printf("idx: %d\n", idx);
free(task->user_data);
free(task);
}
int main(void) {
ThreadPool pool = {0};
nThreadPoolCreate(&pool, THREADPOOL_INIT_COUNT);
// pool --> memset();
int i = 0;
for (i = 0;i < TASK_INIT_SIZE;i ++) {
struct nTask *task = (struct nTask *)malloc(sizeof(struct nTask));
if (task == NULL) {
perror("malloc");
exit(1);
}
memset(task, 0, sizeof(struct nTask));
task->task_func = task_entry;
task->user_data = malloc(sizeof(int));
*(int*)task->user_data = i;
nThreadPoolPushTask(&pool, task);
}
getchar(); //因为主线程创建1000个子线程之后就退出了 所以加一个getchar做停留 等待所有线程执行完成
}
#endif
线程池类似于营业厅 一个柜员对应一个任务 相对的有多个柜员就可以同时执行多个任务 当一个任务只能一个柜员执行 在没有任务的时候 柜员始终保持在线 直到任务来到 开始工作 代码的主要逻辑就是 判断是否有任务 若是有则取出任务队列中的第一个赋予 A指针 同时 队列中删除第一个任务 这样始终保持着 任务队列的第一个是最新的未执行的
注:本次课程使用的方法 pthread_cond_signal 单个线程唤醒;pthread_cond_broadcast 所有线程唤醒;pthread_cond_t 条件等待变量 ;pthread_cond_wait 条件等待
后续关于线程池SDK的调试
因此引入Linux的调试工具 gdb
具体格式如下:
gcc -o xxxxx xxxxx.c -lpthread -g //gdb调试需要写入-g
gdb ./xxxxx //前面需要写入gdb
'''
'''
'''
(GDB) b xx行 //在第几行加上断点通常在判断处
//然后
(gdb) r //r代表接着运行 run
(gdb) c //c代表continue
(gdb) q //按q退出调试
//在使用指针的时候要注意在使用前判断是否为NULL 并且置为0 避免在操作内存的时候 内存中有数据导致数据异常
//类型不对所引起的指针偏移 要注意传入参数要和方法中的类型一致
//主线程没有等到任务结束就退出 加入getchar()干预线程不结束