手写线程池 - C语言版 | 爱编程的大丙 (subingwen.cn)
跟着这个教程学习,总结归纳。
1.线程池原理
创建一个线程,实现很方便。
缺点:若并发的线程数量很多,并且每个线程都是执行一个时间较短的任务就结束了。
由于频繁的创建线程和销毁线程需要时间,这样的频繁创建线程会大大降低
系统的效率。
2.思考 :怎么样实现线程复用,让线程执行完一个任务后不被销毁,还可以继续执行其他的任务?
答:线程池
3.思考 :什么是线程池?
可以点击看爱编程的大丙写的这边文章!写的很好
4.线程池的组成:任务队列、工作的线程、管理者线程
(1)任务队列
听到任务队列之后,咱们在脑海里应该闪现出另外一个模型,就是生产者和消费者模型。因为只要
有任务队列,就离不开生产者和消费者模型。为什么要有任务队列呢?就是因为他有生产者要把生
产出来的数据或者商品进行存储,存储起来之后让对应的某些消费者去消费。在我们写程序的时候,
就是有一些生产者线程还是负责往这个任务队列里边放任务,然后有一些消费者线程负责把任务从
任务队列中取出去,并且把它处理掉。在线程池里边有任务队列,也就意味着这个线程池它是生产者
和消费者模型里边的一部分。哪一个部分呢?这个线程池主要是负责维护一个任务队列,然后再维护
若干个消费者线程。它维护的线程都是消费者线程。
那么生产者是谁呢?谁使用任务队列,谁就是生产者。那么这个生产者它把任务放到任务队列里边。
怎么放呢?肯定是通过线程池提供的api接口,把这个任务放到任务队列,,放进来之后,这个消费
者线程通过一个循环,不停地从这个任务队列里边去取任务,
假设说咱们这个任务队列为空了,消费者线程就需要阻塞了。可以使用条件变量把它阻塞掉就行了。
如果让消费者线程去阻塞,它就放弃了CPU时间片了,这个对系统的资源消耗是一点都没有的。
对于生产者来说,假设这个任务队列已经满了,需要阻塞生产者。当这个消费者消费了这个产品之后,
任务队列就不再满了,那么任务队列不再满之后,咱们就让消费者把这个生产者唤醒,让他们继续生
产。这个任务队列是两个角色,而不是三个角色,这一点要搞明白的!!!
(2)工作的线程
这个任务队列里边的任务都是函数地址,工作的线程在处理这个任务的时候,它就基于这个函数地址
对那个函数进行调用。也就说这个任务队列里边的任务都是回调函数。
什么时候去回调呢?
就是当线程池里边的这个消费者线程把它取出去之后,它就被调用了。如果说没有被取出来,他就不
被调用了。
(3)管理者线程
管理者线程的职责非常单一,就是不停的去检测当前任务队列里边任务的个数,还有当前工作的线程
的线程数,然后针对于它们的数量进行一个运算。看一看现在需要添加线程还是销毁线程。干这个事
的时候,可以给它设置一个频率,比如说你可以让他五秒钟去做一次或者十秒钟去做一次。sleep管理
者是非常轻松的
threadpool.c
//1.任务队列
// 任务结构体
typedef struct Task
{
void (*function)(void* arg);
void* arg;
}Task;
//2.线程池定义
// 线程池结构体
struct ThreadPool
{
// 任务队列
Task* taskQ;
int queueCapacity; // 容量
int queueSize; // 当前任务个数
int queueFront; // 队头 -> 取数据
int queueRear; // 队尾 -> 放数据
//线程池线程:①管理者线程 ②工作的线程(⊙o⊙)…
pthread_t managerID; // 管理者线程ID
pthread_t *threadIDs; // 工作的线程ID
int minNum; // 最小线程数量
int maxNum; // 最大线程数量
int busyNum; // 忙的线程的个数
int liveNum; // 存活的线程的个数
int exitNum; // 要销毁的线程个数
pthread_mutex_t mutexPool; // 锁整个的线程池
pthread_mutex_t mutexBusy; // 锁busyNum变量
pthread_cond_t notFull; // 任务队列是不是满了
pthread_cond_t notEmpty; // 任务队列是不是空了
int shutdown; // 是不是要销毁线程池, 销毁为1, 不销毁为0
};
threadpool.h
#ifndef _THREADPOOL_H
#define _THREADPOOL_H
// 创建线程池并初始化
// 销毁线程池
// 给线程池添加任务
// 获取线程池中工作的线程的个数
// 获取线程池中活着的线程的个数
// 工作的线程(消费者线程)任务函数
// 管理者线程任务函数
// 单个线程退出
#endif // _THREADPOOL_H
一般情况下,main函数所在的线程我们称之为主线程(main线程),其余创建的线程
称之为子线程。
程序中默认只有一个进程,fork()函数调用,2进程
程序中默认只有一个线程,pthread_create()函数调用,2个线程。
#include
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
- 功能:创建一个子线程
- 参数:
- thread:传出参数,线程创建成功后,子线程的线程ID被写到该变量中。
- attr : 设置线程的属性,一般使用默认值,NULL
- start_routine : 函数指针,这个函数是子线程需要处理的逻辑代码
- arg : 给第三个参数使用,传参
- 返回值:
成功:0
失败:返回错误号。这个错误号和之前errno不太一样。
获取错误号的信息: char * strerror(int errnum);
互斥量的类型 pthread_mutex_t
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
- 初始化互斥量
- 参数 :
- mutex : 需要初始化的互斥量变量
- attr : 互斥量相关的属性,NULL
- restrict : C语言的修饰符,被修饰的指针,不能由另外的一个指针进行操作。
pthread_mutex_t *restrict mutex = xxx;
pthread_mutex_t * mutex1 = mutex;
int pthread_mutex_lock(pthread_mutex_t *mutex);
- 加锁,阻塞的,如果有一个线程加锁了,那么其他的线程只能阻塞等待
int pthread_mutex_unlock(pthread_mutex_t *mutex);
- 解锁
条件变量的类型 pthread_cond_t
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
线程池的组成:任务队列、工作的线程、管理者线程
创建线程的步骤:threadPoolCreate(int min,int max,int queueSize)
1.申请线程池内存空间:pool
2.申请工作线程内存空间:pool->threadIDs
(1).初始化工作线程:memset(pool->threadIDs,0,sizeof(pthread_t) * max)
(2).初始化工作线程的相关变量:
pool->minNum = min;
pool->maxNum = max;
pool->busyNum = 0;
pool->liveNum = min; //和最小个数相等
pool->exitNum = 0;
(3).初始化互斥锁和条件变量
pthread_mutex_init(&pool->mutexPool,NULL);
pthread_mutex_init(&pool->mutexBusy,NULL);
pthread_cond_init(&pool->notEmpty,NULL);
pthread_cond_init(&pool->notFull,NULL);
3.申请任务队列空间:pool->taskQ = (Task*)malloc(sizeof(Task) * queueSize)
(1).初始化任务队列的相关变量
pool->queueCapacity = queueCapacity;
pool->queueSize = queueSize;
pool->queueFront= 0;
pool->queueRear= 0;
4.初始化销毁线程池:pool->shutdown = 0;
5.创建一个管理者线程:pthread_create(&pool->managerID,NULL,manager,pool);
6.创建多个工作线程
for(int i=0;i
pthread_create(&pool->threadIDs[i],NULL,worker,pool);
}
7.释放资源
if(pool && pool->threadIDs) free(pool->threadIDs);
if(pool && pool->taskQ) free(pool->taskQ);
if(pool) free(pool);
threadpool.c
ThreadPool* threadPoolCreate(int min, int max, int queueCapacity)
{
//申请线程池内存空间
ThreadPool* pool = (ThreadPool*)malloc(sizeof(ThreadPool));
do
{
if (pool == NULL)
{
printf("malloc threadpool fail...\n");
break;
}
//申请工作线程内存空间
pool->threadIDs = (pthread_t*)malloc(sizeof(pthread_t) * max);
if (pool->threadIDs == NULL)
{
printf("malloc threadIDs fail...\n");
break;
}
//初始化工作线程
memset(pool->threadIDs, 0, sizeof(pthread_t) * max);
//初始化工作线程的相关变量
pool->minNum = min;
pool->maxNum = max;
pool->busyNum = 0;
pool->liveNum = min; // 和最小个数相等
pool->exitNum = 0;
//初始化互斥锁和条件变量
if (pthread_mutex_init(&pool->mutexPool, NULL) != 0 ||
pthread_mutex_init(&pool->mutexBusy, NULL) != 0 ||
pthread_cond_init(&pool->notEmpty, NULL) != 0 ||
pthread_cond_init(&pool->notFull, NULL) != 0)
{
printf("mutex or condition init fail...\n");
break;
}
// 申请任务队列空间
pool->taskQ = (Task*)malloc(sizeof(Task) * queueCapacity);
// 初始化任务队列相关变量
pool->queueCapacity = queueCapacity;
pool->queueSize = 0;
pool->queueFront = 0;
pool->queueRear = 0;
//初始化销毁线程池为0
pool->shutdown = 0;
// 创建线程:管理者线程、工作线程
// 创建一个管理者线程
pthread_create(&pool->managerID, NULL, manager, pool);
// 创建多个工作线程
for (int i = 0; i < min; ++i)
{
pthread_create(&pool->threadIDs[i], NULL, worker, pool);
}
return pool;
} while (0);
// 释放资源
if (pool && pool->threadIDs) free(pool->threadIDs);
if (pool && pool->taskQ) free(pool->taskQ);
if (pool) free(pool);
return NULL;
}
threadpool.h
typedef struct ThreadPool ThreadPool;
// 创建线程池并初始化
ThreadPool *threadPoolCreate(int min, int max, int queueCapacity);
条件变量的类型 pthread_cond_t
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
- 等待,调用了该函数,线程会阻塞。
int pthread_cond_signal(pthread_cond_t *cond);
- 唤醒一个或者多个等待的线程
1.判断当前任务队列是否为空,若任务队列为空且线程池没有被销毁,那么就阻塞工作线程。
while(pool->queueSzie == 0 && !pool->shutdown) { // 阻塞工作线程 pthread_cond_wait(&pool->notEmpty,&pool->mutexPool); ... } ... ... ...
比如说有十个线程被唤醒了,其中的某一个线程判定任务队列不为空它就继续向下执行消费,消费完了之后,发现任务队列为空了,那么其他线程再回来判定的时候为空,那么就会被阻塞在pthread_cond_wait(&pool->notEmpty,&pool->mutexPool);
2.判断线程池是否被关闭了,如果是的话,那么当前线程退出。
// 判断线程池是否被关闭了 if (pool->shutdown) { pthread_mutex_unlock(&pool->mutexPool);//打开互斥锁,避免死锁 threadExit(pool);//当前的线程退出 }
3.如果没有被关闭,那就开始消费,从任务队列中取出一个任务,移动头结点(循环队列),然后通知生产者生产和解锁。
// 开始消费........................ // 从任务队列中取出一个任务 Task task; task.function = pool->taskQ[pool->queueFront].function; task.arg = pool->taskQ[pool->queueFront].arg; // 移动头结点(循环队列) pool->queueFront = (pool->queueFront + 1) % pool->queueCapacity; pool->queueSize--; // 通知生产者生产和解锁 pthread_cond_signal(&pool->notFull); pthread_mutex_unlock(&pool->mutexPool);
4.忙的线程个数:busyNum+1
printf("thread %ld start working...\n", pthread_self()); pthread_mutex_lock(&pool->mutexBusy); pool->busyNum++; pthread_mutex_unlock(&pool->mutexBusy);
5.然后调用函数,之后释放堆内存
//调用函数 task.function(task.arg);// 等价(*task.function)(task.arg); free(task.arg); task.arg = NULL;
6.最后忙的线程个数:busyNum--
printf("thread %ld end working...\n", pthread_self()); pthread_mutex_lock(&pool->mutexBusy); pool->busyNum--; pthread_mutex_unlock(&pool->mutexBusy);
threadpool.c
void* worker(void* arg)
{
ThreadPool* pool = (ThreadPool*)arg;
while (1)
{
pthread_mutex_lock(&pool->mutexPool);
// 当前任务队列是否为空
while (pool->queueSize == 0 && !pool->shutdown)
{
// 阻塞工作线程
pthread_cond_wait(&pool->notEmpty, &pool->mutexPool);
// 判断是不是要销毁线程
if (pool->exitNum > 0)
{
pool->exitNum--;
if (pool->liveNum > pool->minNum)
{
pool->liveNum--;
pthread_mutex_unlock(&pool->mutexPool);
threadExit(pool);//当前的线程退出
}
}
}
// 判断线程池是否被关闭了
if (pool->shutdown)
{
pthread_mutex_unlock(&pool->mutexPool);//打开互斥锁,避免死锁
threadExit(pool);//当前的线程退出
}
// 开始消费........................
// 从任务队列中取出一个任务
Task task;
task.function = pool->taskQ[pool->queueFront].function;
task.arg = pool->taskQ[pool->queueFront].arg;
// 移动头结点(循环队列)
pool->queueFront = (pool->queueFront + 1) % pool->queueCapacity;
pool->queueSize--;
// 通知生产者生产和解锁
pthread_cond_signal(&pool->notFull);
pthread_mutex_unlock(&pool->mutexPool);
printf("thread %ld start working...\n", pthread_self());
pthread_mutex_lock(&pool->mutexBusy);
pool->busyNum++;
pthread_mutex_unlock(&pool->mutexBusy);
//调用函数
task.function(task.arg);// 等价(*task.function)(task.arg);
free(task.arg);
task.arg = NULL;
printf("thread %ld end working...\n", pthread_self());
pthread_mutex_lock(&pool->mutexBusy);
pool->busyNum--;
pthread_mutex_unlock(&pool->mutexBusy);
}
return NULL;
}
threadpool.h
// 工作的线程(消费者线程)任务函数
void* worker(void* arg);
管理者线程的职责非常单一,就是不停的去检测当前任务队列里边任务的个数,还有当前工作的线程的线程数,然后针对于它们的数量进行一个运算。看一看现在需要添加线程还是销毁线程。干这个事的时候,可以给它设置一个频率,比如说你可以让他五秒钟去做一次或者十秒钟去做一次。
定义全局变量:
const int NUMBER = 2;
1.取出线程池中任务的数量和当前线程的数量
// 取出线程池中任务的数量和当前线程的数量 pthread_mutex_lock(&pool->mutexPool); int queueSize = pool->queueSize; int liveNum = pool->liveNum; pthread_mutex_unlock(&pool->mutexPool);
2.取出忙的线程的数量
// 取出忙的线程的数量 pthread_mutex_lock(&pool->mutexBusy); int busyNum = pool->busyNum; pthread_mutex_unlock(&pool->mutexBusy);
3.添加线程
当任务的个数>存活的线程个数,且存活的线程数<最大线程数,说明当前线程池里的存活的线程相对较少,我们可以添加线程。
// 添加线程 // 任务的个数>存活的线程个数 && 存活的线程数<最大线程数 if (queueSize > liveNum && liveNum < pool->maxNum) { ... }
添加NUMBER个线程,和判断工作线程pool->threadIDs中有没有等于0的线程,也就是
for (int i = 0; i < pool->maxNum && counter < NUMBER && pool->liveNum < pool->maxNum; ++i) { ... if (pool->threadIDs[i] == 0) { ... } ... }
满足条件后就创建线程,pthread_create(&pool->threadIDs[i], NULL, worker, pool);然后线程池存活的个数+1:pool->liveNum++;
// 添加线程 // 任务的个数>存活的线程个数 && 存活的线程数<最大线程数 if (queueSize > liveNum && liveNum < pool->maxNum) { pthread_mutex_lock(&pool->mutexPool); int counter = 0; for (int i = 0; i < pool->maxNum && counter < NUMBER && pool->liveNum < pool->maxNum; ++i) { if (pool->threadIDs[i] == 0) { pthread_create(&pool->threadIDs[i], NULL, worker, pool); counter++; pool->liveNum++; } } pthread_mutex_unlock(&pool->mutexPool); }
4.销毁线程
当忙的线程 * 2 < 存活的线程数 && 存活的线程 > 最小线程数 ,说明存活的线程数较多,我们可以销毁掉NUMBER个线程。销毁线程的思路是让工作的线程自杀,可以唤醒工作线程,因为工作线程是阻塞在这个notEmpty这个条件变量上 。
// 销毁线程 // 忙的线程*2 < 存活的线程数 && 存活的线程>最小线程数 if (busyNum * 2 < liveNum && liveNum > pool->minNum) { pthread_mutex_lock(&pool->mutexPool); pool->exitNum = NUMBER; pthread_mutex_unlock(&pool->mutexPool); // 让工作的线程自杀 for (int i = 0; i < NUMBER; ++i) { // 唤醒工作线程,因为工作线程是阻塞在这个notEmpty这个条件变量上 pthread_cond_signal(&pool->notEmpty); } }
思考:如何实现唤醒工作线程,就可以让工作线程自杀呢?
我们可以在void* worker(void* arg)中判断是不是要销毁进程。由于这些没有工作的线程都阻塞在pool->notEmpty这个条件变量了,我们可以把这些阻塞在条件变量上的线程唤醒,直接让它向下执行。要想让它去自杀,让它退出去就行了。退出当前线程的函数是threadExit(pool),接下来会讲到。
这个pool->exitNum--;是不能放到void* worker(void* arg) { ThreadPool* pool = (ThreadPool*)arg; while (1) { pthread_mutex_lock(&pool->mutexPool); // 当前任务队列是否为空 while (pool->queueSize == 0 && !pool->shutdown) { // 阻塞工作线程 pthread_cond_wait(&pool->notEmpty, &pool->mutexPool); // 判断是不是要销毁线程 if (pool->exitNum > 0) { pool->exitNum--; if (pool->liveNum > pool->minNum) { pool->liveNum--; pthread_mutex_unlock(&pool->mutexPool); threadExit(pool);//当前的线程退出 } } } ... } }
里边的。假设你放进去了,这个管理者线程,唤醒了2个工作线程,但是这个条件if(pools->liveNum > pool->minNum) { ... }
一直没有成立,此时exitNum还等于2。在这种情况下生产者往任务队列里添加了新的任务,它唤醒了工作的线程,这个工作的线程没去干活而是自杀了,这是不符合我们的预期的。因此,这个管理者线程唤醒了两次工作的线程,不管工作的线程有没有自杀,我们都需要让exitNum恢复为0if(pools->liveNum > pool->minNum)
threadpool.c
void* manager(void* arg)
{
ThreadPool* pool = (ThreadPool*)arg;
while (!pool->shutdown)
{
// 每隔3s检测一次
sleep(3);
// 取出线程池中任务的数量和当前线程的数量
pthread_mutex_lock(&pool->mutexPool);
int queueSize = pool->queueSize;
int liveNum = pool->liveNum;
pthread_mutex_unlock(&pool->mutexPool);
// 取出忙的线程的数量
pthread_mutex_lock(&pool->mutexBusy);
int busyNum = pool->busyNum;
pthread_mutex_unlock(&pool->mutexBusy);
// 添加线程
// 任务的个数>存活的线程个数 && 存活的线程数<最大线程数
if (queueSize > liveNum && liveNum < pool->maxNum)
{
pthread_mutex_lock(&pool->mutexPool);
int counter = 0;
for (int i = 0; i < pool->maxNum && counter < NUMBER
&& pool->liveNum < pool->maxNum; ++i)
{
if (pool->threadIDs[i] == 0)
{
pthread_create(&pool->threadIDs[i], NULL, worker, pool);
counter++;
pool->liveNum++;
}
}
pthread_mutex_unlock(&pool->mutexPool);
}
// 销毁线程
// 忙的线程*2 < 存活的线程数 && 存活的线程>最小线程数
if (busyNum * 2 < liveNum && liveNum > pool->minNum)
{
pthread_mutex_lock(&pool->mutexPool);
pool->exitNum = NUMBER;
pthread_mutex_unlock(&pool->mutexPool);
// 让工作的线程自杀
for (int i = 0; i < NUMBER; ++i)
{
pthread_cond_signal(&pool->notEmpty);
}
}
}
return NULL;
}
threadpool.h
// 管理者线程任务函数
void* manager(void* arg);
#include
void pthread_exit(void *retval);
功能:终止一个线程,在哪个线程中调用,就表示终止哪个线程
参数:
retval:需要传递一个指针,作为一个返回值,可以在pthread_join()中获取到。
pthread_t pthread_self(void);
功能:获取当前的线程的线程ID
假设有这么一种情况,这个线程被创建出来了,然后在里面存了他的线程id,后面这个线程又退出了,如果退出之后,这个线程在这个threadIDs数组,里边的元素值需要重新被修改为0。我们才能够重复利用threadIDs里边这个元素
threadpool.c
void threadExit(ThreadPool* pool)
{
pthread_t tid = pthread_self();
for (int i = 0; i < pool->maxNum; ++i)
{
if (pool->threadIDs[i] == tid)
{
pool->threadIDs[i] = 0;
printf("threadExit() called, %ld exiting...\n", tid);
break;
}
}
pthread_exit(NULL);
}
threadpool.h
// 单个线程退出
void threadExit(ThreadPool* pool);
1.当前任务个数 == 任务队列容量 且 线程池未关闭,说明,当前不能生产产品了,那么就要阻塞生产者线程。
while (pool->queueSize == pool->queueCapacity && !pool->shutdown) { // 阻塞生产者线程 pthread_cond_wait(&pool->notFull, &pool->mutexPool); }
2.判断线程池是否关闭,若pool->shutdown=1,则打开互斥锁,避免死锁
if (pool->shutdown) { pthread_mutex_unlock(&pool->mutexPool); return; }
3.若当前任务个数 != 任务队列容量 且 线程池未关闭,说明可以添加任务到任务队列中
// 添加任务 pool->taskQ[pool->queueRear].function = func; pool->taskQ[pool->queueRear].arg = arg; pool->queueRear = (pool->queueRear + 1) % pool->queueCapacity; pool->queueSize++;
4.之后,需要唤醒消费者线程去消费产品
//唤醒消费线程 pthread_cond_signal(&pool->notEmpty); pthread_mutex_unlock(&pool->mutexPool);
threadpool.c
void threadPoolAdd(ThreadPool* pool, void(*func)(void*), void* arg)
{
pthread_mutex_lock(&pool->mutexPool);
while (pool->queueSize == pool->queueCapacity && !pool->shutdown)
{
// 阻塞生产者线程
pthread_cond_wait(&pool->notFull, &pool->mutexPool);
}
if (pool->shutdown)
{
pthread_mutex_unlock(&pool->mutexPool);
return;
}
// 添加任务
pool->taskQ[pool->queueRear].function = func;
pool->taskQ[pool->queueRear].arg = arg;
pool->queueRear = (pool->queueRear + 1) % pool->queueCapacity;
pool->queueSize++;
//唤醒消费线程
pthread_cond_signal(&pool->notEmpty);
pthread_mutex_unlock(&pool->mutexPool);
}
threadpool.h
// 给线程池添加任务
void threadPoolAdd(ThreadPool* pool, void(*func)(void*), void* arg);
threadpool.c
int threadPoolBusyNum(ThreadPool* pool)
{
pthread_mutex_lock(&pool->mutexBusy);
int busyNum = pool->busyNum;
pthread_mutex_unlock(&pool->mutexBusy);
return busyNum;
}
int threadPoolAliveNum(ThreadPool* pool)
{
pthread_mutex_lock(&pool->mutexPool);
int aliveNum = pool->liveNum;
pthread_mutex_unlock(&pool->mutexPool);
return aliveNum;
}
threadpool.h
// 获取线程池中工作的线程的个数
int threadPoolBusyNum(ThreadPool* pool);
// 获取线程池中活着的线程的个数
int threadPoolAliveNum(ThreadPool* pool);
#include
int pthread_join(pthread_t thread, void **retval);
- 功能:和一个已经终止的线程进行连接
回收子线程的资源
这个函数是阻塞函数,调用一次只能回收一个子线程
一般在主线程中使用
- 参数:
- thread:需要回收的子线程的ID
- retval: 接收子线程退出时的返回值
- 返回值:
0 : 成功
非0 : 失败,返回的错误号
互斥量的类型 pthread_mutex_t
int pthread_mutex_destroy(pthread_mutex_t *mutex);
- 释放互斥量的资源
条件变量的类型 pthread_cond_t
int pthread_cond_destroy(pthread_cond_t *cond);
1.关闭线程池,由于管理者线程是根据线程池的关闭状态进行while循环的,所以当pool->shutdown=1,则退出循环了,意味着manager就执行完毕了,执行完毕之后管理者线程就退出了,因此咱们需要回收管理者线程。
// 关闭线程池 pool->shutdown = 1; // 阻塞回收管理者线程 pthread_join(pool->managerID, NULL);
2.唤醒阻塞的消费者线程:
把void* worker(void* arg)中被阻塞在pthread_cond_wait(&pool->notEmpty,&pool->mutexPool)的线程解除阻塞,那么当前线程就向下执行,判断线程池是否被关闭了,如果被关闭了,那么就打开互斥锁,然后让当前的线程退出。
// 唤醒阻塞的消费者线程 for (int i = 0; i < pool->liveNum; ++i) { pthread_cond_signal(&pool->notEmpty); }
void* worker(void* arg) { ThreadPool* pool = (ThreadPool*)arg; while (1) { pthread_mutex_lock(&pool->mutexPool); // 当前任务队列是否为空 while (pool->queueSize == 0 && !pool->shutdown) { // 阻塞工作线程 pthread_cond_wait(&pool->notEmpty, &pool->mutexPool); ... } // 判断线程池是否被关闭了 if (pool->shutdown) { pthread_mutex_unlock(&pool->mutexPool);//打开互斥锁,避免死锁 threadExit(pool);//当前的线程退出 } ... } }
3.释放堆内存:释放任务队列和工作的线程
// 释放堆内存 if (pool->taskQ) { free(pool->taskQ); } if (pool->threadIDs) { free(pool->threadIDs); }
4.释放互斥量和条件变量的资源
pthread_mutex_destroy(&pool->mutexPool); pthread_mutex_destroy(&pool->mutexBusy); pthread_cond_destroy(&pool->notEmpty); pthread_cond_destroy(&pool->notFull);
5.释放线程池
free(pool); pool = NULL;
threadpool.c
int threadPoolDestroy(ThreadPool* pool)
{
if (pool == NULL)
{
return -1;
}
// 关闭线程池
pool->shutdown = 1;
// 阻塞回收管理者线程
pthread_join(pool->managerID, NULL);
// 唤醒阻塞的消费者线程
for (int i = 0; i < pool->liveNum; ++i)
{
pthread_cond_signal(&pool->notEmpty);
}
// 释放堆内存
if (pool->taskQ)
{
free(pool->taskQ);
}
if (pool->threadIDs)
{
free(pool->threadIDs);
}
pthread_mutex_destroy(&pool->mutexPool);
pthread_mutex_destroy(&pool->mutexBusy);
pthread_cond_destroy(&pool->notEmpty);
pthread_cond_destroy(&pool->notFull);
free(pool);
pool = NULL;
return 0;
}
threadpool.h
// 销毁线程池
int threadPoolDestroy(ThreadPool* pool);
threadpool.h
#ifndef _THREADPOOL_H
#define _THREADPOOL_H
typedef struct ThreadPool ThreadPool;
// 创建线程池并初始化
ThreadPool *threadPoolCreate(int min, int max, int queueCapacity);
// 销毁线程池
int threadPoolDestroy(ThreadPool* pool);
// 给线程池添加任务
void threadPoolAdd(ThreadPool* pool, void(*func)(void*), void* arg);
// 获取线程池中工作的线程的个数
int threadPoolBusyNum(ThreadPool* pool);
// 获取线程池中活着的线程的个数
int threadPoolAliveNum(ThreadPool* pool);
//
// 工作的线程(消费者线程)任务函数
void* worker(void* arg);
// 管理者线程任务函数
void* manager(void* arg);
// 单个线程退出
void threadExit(ThreadPool* pool);
#endif // _THREADPOOL_H
threadpool.c
#include
#include
#include
#include
#include
#include "threadpool.h"
const int NUMBER = 2;
// 任务结构体
typedef struct Task
{
void (*function)(void* arg);
void* arg;
}Task;
// 线程池结构体
struct ThreadPool
{
// 任务队列
Task* taskQ;
int queueCapacity; // 容量
int queueSize; // 当前任务个数
int queueFront; // 队头 -> 取数据
int queueRear; // 队尾 -> 放数据
pthread_t managerID; // 管理者线程ID
pthread_t *threadIDs; // 工作的线程ID
int minNum; // 最小线程数量
int maxNum; // 最大线程数量
int busyNum; // 忙的线程的个数
int liveNum; // 存活的线程的个数
int exitNum; // 要销毁的线程个数
pthread_mutex_t mutexPool; // 锁整个的线程池
pthread_mutex_t mutexBusy; // 锁busyNum变量
pthread_cond_t notFull; // 任务队列是不是满了
pthread_cond_t notEmpty; // 任务队列是不是空了
int shutdown; // 是不是要销毁线程池, 销毁为1, 不销毁为0
};
// 创建线程池并初始化
ThreadPool* threadPoolCreate(int min, int max, int queueCapacity)
{
ThreadPool* pool = (ThreadPool*)malloc(sizeof(ThreadPool));
do
{
if (pool == NULL)
{
printf("malloc threadpool fail...\n");
break;
}
pool->threadIDs = (pthread_t*)malloc(sizeof(pthread_t) * max);
if (pool->threadIDs == NULL)
{
printf("malloc threadIDs fail...\n");
break;
}
memset(pool->threadIDs, 0, sizeof(pthread_t) * max);
pool->minNum = min;
pool->maxNum = max;
pool->busyNum = 0;
pool->liveNum = min; // 和最小个数相等
pool->exitNum = 0;
if (pthread_mutex_init(&pool->mutexPool, NULL) != 0 ||
pthread_mutex_init(&pool->mutexBusy, NULL) != 0 ||
pthread_cond_init(&pool->notEmpty, NULL) != 0 ||
pthread_cond_init(&pool->notFull, NULL) != 0)
{
printf("mutex or condition init fail...\n");
break;
}
// 任务队列
pool->taskQ = (Task*)malloc(sizeof(Task) * queueCapacity);
pool->queueCapacity = queueCapacity;
pool->queueSize = 0;
pool->queueFront = 0;
pool->queueRear = 0;
pool->shutdown = 0;
// 创建线程
pthread_create(&pool->managerID, NULL, manager, pool);
for (int i = 0; i < min; ++i)
{
pthread_create(&pool->threadIDs[i], NULL, worker, pool);
}
return pool;
} while (0);
// 释放资源
if (pool && pool->threadIDs) free(pool->threadIDs);
if (pool && pool->taskQ) free(pool->taskQ);
if (pool) free(pool);
return NULL;
}
// 销毁线程池
int threadPoolDestroy(ThreadPool* pool)
{
if (pool == NULL)
{
return -1;
}
// 关闭线程池
pool->shutdown = 1;
// 阻塞回收管理者线程
pthread_join(pool->managerID, NULL);
// 唤醒阻塞的消费者线程
for (int i = 0; i < pool->liveNum; ++i)
{
pthread_cond_signal(&pool->notEmpty);
}
// 释放堆内存
if (pool->taskQ)
{
free(pool->taskQ);
}
if (pool->threadIDs)
{
free(pool->threadIDs);
}
pthread_mutex_destroy(&pool->mutexPool);
pthread_mutex_destroy(&pool->mutexBusy);
pthread_cond_destroy(&pool->notEmpty);
pthread_cond_destroy(&pool->notFull);
free(pool);
pool = NULL;
return 0;
}
// 给线程池添加任务
void threadPoolAdd(ThreadPool* pool, void(*func)(void*), void* arg)
{
pthread_mutex_lock(&pool->mutexPool);
while (pool->queueSize == pool->queueCapacity && !pool->shutdown)
{
// 阻塞生产者线程
pthread_cond_wait(&pool->notFull, &pool->mutexPool);
}
if (pool->shutdown)
{
pthread_mutex_unlock(&pool->mutexPool);
return;
}
// 添加任务
pool->taskQ[pool->queueRear].function = func;
pool->taskQ[pool->queueRear].arg = arg;
pool->queueRear = (pool->queueRear + 1) % pool->queueCapacity;
pool->queueSize++;
pthread_cond_signal(&pool->notEmpty);
pthread_mutex_unlock(&pool->mutexPool);
}
// 获取线程池中工作的线程的个数
int threadPoolBusyNum(ThreadPool* pool)
{
pthread_mutex_lock(&pool->mutexBusy);
int busyNum = pool->busyNum;
pthread_mutex_unlock(&pool->mutexBusy);
return busyNum;
}
// 获取线程池中活着的线程的个数
int threadPoolAliveNum(ThreadPool* pool)
{
pthread_mutex_lock(&pool->mutexPool);
int aliveNum = pool->liveNum;
pthread_mutex_unlock(&pool->mutexPool);
return aliveNum;
}
// 工作的线程(消费者线程)任务函数
void* worker(void* arg)
{
ThreadPool* pool = (ThreadPool*)arg;
while (1)
{
pthread_mutex_lock(&pool->mutexPool);
// 当前任务队列是否为空
while (pool->queueSize == 0 && !pool->shutdown)
{
// 阻塞工作线程
pthread_cond_wait(&pool->notEmpty, &pool->mutexPool);
// 判断是不是要销毁线程
if (pool->exitNum > 0)
{
pool->exitNum--;
if (pool->liveNum > pool->minNum)
{
pool->liveNum--;
pthread_mutex_unlock(&pool->mutexPool);
threadExit(pool);
}
}
}
// 判断线程池是否被关闭了
if (pool->shutdown)
{
pthread_mutex_unlock(&pool->mutexPool);
threadExit(pool);
}
// 从任务队列中取出一个任务
Task task;
task.function = pool->taskQ[pool->queueFront].function;
task.arg = pool->taskQ[pool->queueFront].arg;
// 移动头结点
pool->queueFront = (pool->queueFront + 1) % pool->queueCapacity;
pool->queueSize--;
// 解锁
pthread_cond_signal(&pool->notFull);
pthread_mutex_unlock(&pool->mutexPool);
printf("thread %ld start working...\n", pthread_self());
pthread_mutex_lock(&pool->mutexBusy);
pool->busyNum++;
pthread_mutex_unlock(&pool->mutexBusy);
task.function(task.arg);
free(task.arg);
task.arg = NULL;
printf("thread %ld end working...\n", pthread_self());
pthread_mutex_lock(&pool->mutexBusy);
pool->busyNum--;
pthread_mutex_unlock(&pool->mutexBusy);
}
return NULL;
}
// 管理者线程任务函数
void* manager(void* arg)
{
ThreadPool* pool = (ThreadPool*)arg;
while (!pool->shutdown)
{
// 每隔3s检测一次
sleep(3);
// 取出线程池中任务的数量和当前线程的数量
pthread_mutex_lock(&pool->mutexPool);
int queueSize = pool->queueSize;
int liveNum = pool->liveNum;
pthread_mutex_unlock(&pool->mutexPool);
// 取出忙的线程的数量
pthread_mutex_lock(&pool->mutexBusy);
int busyNum = pool->busyNum;
pthread_mutex_unlock(&pool->mutexBusy);
// 添加线程
// 任务的个数>存活的线程个数 && 存活的线程数<最大线程数
if (queueSize > liveNum && liveNum < pool->maxNum)
{
pthread_mutex_lock(&pool->mutexPool);
int counter = 0;
for (int i = 0; i < pool->maxNum && counter < NUMBER
&& pool->liveNum < pool->maxNum; ++i)
{
if (pool->threadIDs[i] == 0)
{
pthread_create(&pool->threadIDs[i], NULL, worker, pool);
counter++;
pool->liveNum++;
}
}
pthread_mutex_unlock(&pool->mutexPool);
}
// 销毁线程
// 忙的线程*2 < 存活的线程数 && 存活的线程>最小线程数
if (busyNum * 2 < liveNum && liveNum > pool->minNum)
{
pthread_mutex_lock(&pool->mutexPool);
pool->exitNum = NUMBER;
pthread_mutex_unlock(&pool->mutexPool);
// 让工作的线程自杀
for (int i = 0; i < NUMBER; ++i)
{
pthread_cond_signal(&pool->notEmpty);
}
}
}
return NULL;
}
// 单个线程退出
void threadExit(ThreadPool* pool)
{
pthread_t tid = pthread_self();
for (int i = 0; i < pool->maxNum; ++i)
{
if (pool->threadIDs[i] == tid)
{
pool->threadIDs[i] = 0;
printf("threadExit() called, %ld exiting...\n", tid);
break;
}
}
pthread_exit(NULL);
}
main.c
void taskFunc(void* arg)
{
int num = *(int*)arg;
printf("thread %ld is working, number = %d\n",
pthread_self(), num);
sleep(1);
}
int main()
{
// 创建线程池
ThreadPool* pool = threadPoolCreate(3, 10, 100);
for (int i = 0; i < 100; ++i)
{
int* num = (int*)malloc(sizeof(int));
*num = i + 100;
threadPoolAdd(pool, taskFunc, num);
}
sleep(30);
threadPoolDestroy(pool);
return 0;
}
命令和执行结果:
sudo service apport stop
ulimit -c unlimited
gcc -c threadpool.c -o threadpool.o
ar -rcs libadd.a threadpool.o
gcc main.c -o a.out libadd.a -lpthread
./a.out
生成core文件后,gdb a.out
gdb调试:core-file core
heheda@heheda:~/Linux/threadpool$ sudo service apport stop
heheda@heheda:~/Linux/threadpool$ ulimit -c unlimited
heheda@heheda:~/Linux/threadpool$ gcc -c threadpool.c -o threadpool.o
heheda@heheda:~/Linux/threadpool$ ar -rcs libadd.a threadpool.o
heheda@heheda:~/Linux/threadpool$ gcc main.c -o a.out libadd.a -lpthread
heheda@heheda:~/Linux/threadpool$ ./a.out
thread 140282044466944 start working...
thread 140282044466944 is working, number = 100
thread 140282052859648 start working...
thread 140282052859648 is working, number = 101
thread 140282061252352 start working...
thread 140282061252352 is working, number = 102
thread 140282044466944 end working...
thread 140282052859648 end working...
thread 140282052859648 start working...
thread 140282052859648 is working, number = 104
thread 140282044466944 start working...
thread 140282044466944 is working, number = 103
thread 140282061252352 end working...
thread 140282061252352 start working...
thread 140282061252352 is working, number = 105
thread 140282044466944 end working...
thread 140282044466944 start working...
thread 140282044466944 is working, number = 106
thread 140282052859648 end working...
thread 140282052859648 start working...
thread 140282052859648 is working, number = 107
thread 140282061252352 end working...
thread 140282061252352 start working...
thread 140282061252352 is working, number = 108
thread 140282036074240 start working...
thread 140282036074240 is working, number = 109
thread 140281819166464 start working...
thread 140281819166464 is working, number = 110
thread 140282052859648 end working...
thread 140282052859648 start working...
thread 140282052859648 is working, number = 111
thread 140282044466944 end working...
thread 140282044466944 start working...
thread 140282044466944 is working, number = 112
thread 140282061252352 end working...
thread 140282061252352 start working...
thread 140282061252352 is working, number = 113
thread 140282036074240 end working...
thread 140282044466944 end working...
thread 140282044466944 start working...
thread 140282044466944 is working, number = 114
thread 140282052859648 end working...
thread 140282061252352 end working...
thread 140282061252352 start working...
thread 140282061252352 is working, number = 116
thread 140282052859648 start working...
thread 140282052859648 is working, number = 117
thread 140281819166464 end working...
thread 140281819166464 start working...
thread 140281819166464 is working, number = 118
thread 140282036074240 start working...
thread 140282036074240 is working, number = 115
thread 140281819166464 end working...
thread 140281819166464 start working...
thread 140282044466944 end working...
thread 140282044466944 start working...
thread 140282044466944 is working, number = 120
thread 140282061252352 end working...
thread 140282061252352 start working...
thread 140282061252352 is working, number = 121
thread 140282036074240 end working...
thread 140282036074240 start working...
thread 140282036074240 is working, number = 122
thread 140281819166464 is working, number = 119
thread 140282052859648 end working...
thread 140282052859648 start working...
thread 140282052859648 is working, number = 123
thread 140281810773760 start working...
thread 140281810773760 is working, number = 124
thread 140281802381056 start working...
thread 140281802381056 is working, number = 125
thread 140282036074240 end working...
thread 140282036074240 start working...
thread 140282036074240 is working, number = 126
thread 140282061252352 end working...
thread 140281819166464 end working...
thread 140281819166464 start working...
thread 140281819166464 is working, number = 128
thread 140282061252352 start working...
thread 140282061252352 is working, number = 127
thread 140282052859648 end working...
thread 140282052859648 start working...
thread 140282052859648 is working, number = 129
thread 140282044466944 end working...
thread 140282044466944 start working...
thread 140282044466944 is working, number = 130
thread 140282036074240 end working...
thread 140282036074240 start working...
thread 140282036074240 is working, number = 131
thread 140281819166464 end working...
thread 140281819166464 start working...
thread 140281819166464 is working, number = 132
thread 140281810773760 end working...
thread 140281810773760 start working...
thread 140281810773760 is working, number = 133
thread 140282052859648 end working...
thread 140282052859648 start working...
thread 140282052859648 is working, number = 134
thread 140282044466944 end working...
thread 140282044466944 start working...
thread 140282044466944 is working, number = 135
thread 140281802381056 end working...
thread 140281802381056 start working...
thread 140281802381056 is working, number = 136
thread 140282061252352 end working...
thread 140282061252352 start working...
thread 140282061252352 is working, number = 137
thread 140282036074240 end working...
thread 140282044466944 end working...
thread 140282044466944 start working...
thread 140282044466944 is working, number = 139
thread 140282061252352 end working...
thread 140282061252352 start working...
thread 140282052859648 end working...
thread 140281819166464 end working...
thread 140281819166464 start working...
thread 140281819166464 is working, number = 141
thread 140282052859648 start working...
thread 140282036074240 start working...
thread 140282061252352 is working, number = 140
thread 140282036074240 is working, number = 138
thread 140281802381056 end working...
thread 140282052859648 is working, number = 142
thread 140281802381056 start working...
thread 140281802381056 is working, number = 143
thread 140281810773760 end working...
thread 140281810773760 start working...
thread 140281810773760 is working, number = 144
thread 140281793988352 start working...
thread 140281793988352 is working, number = 145
thread 140281785595648 start working...
thread 140281785595648 is working, number = 146
thread 140281810773760 end working...
thread 140281810773760 start working...
thread 140281810773760 is working, number = 147
thread 140282044466944 end working...
thread 140282044466944 start working...
thread 140282044466944 is working, number = 148
thread 140281819166464 end working...
thread 140281819166464 start working...
thread 140281819166464 is working, number = 149
thread 140281802381056 end working...
thread 140281802381056 start working...
thread 140281802381056 is working, number = 150
thread 140282036074240 end working...
thread 140282036074240 start working...
thread 140282036074240 is working, number = 151
thread 140282052859648 end working...
thread 140282052859648 start working...
thread 140282052859648 is working, number = 152
thread 140282061252352 end working...
thread 140282061252352 start working...
thread 140282061252352 is working, number = 153
thread 140281793988352 end working...
thread 140281793988352 start working...
thread 140281793988352 is working, number = 154
thread 140281785595648 end working...
thread 140281785595648 start working...
thread 140281785595648 is working, number = 155
thread 140281810773760 end working...
thread 140281810773760 start working...
thread 140281810773760 is working, number = 156
thread 140282044466944 end working...
thread 140282044466944 start working...
thread 140282044466944 is working, number = 157
thread 140282052859648 end working...
thread 140282036074240 end working...
thread 140282036074240 start working...
thread 140282036074240 is working, number = 158
thread 140282052859648 start working...
thread 140282052859648 is working, number = 159
thread 140282061252352 end working...
thread 140282061252352 start working...
thread 140282061252352 is working, number = 160
thread 140281819166464 end working...
thread 140281819166464 start working...
thread 140281819166464 is working, number = 161
thread 140281802381056 end working...
thread 140281802381056 start working...
thread 140281802381056 is working, number = 162
thread 140281785595648 end working...
thread 140281785595648 start working...
thread 140281785595648 is working, number = 163
thread 140281793988352 end working...
thread 140281793988352 start working...
thread 140281793988352 is working, number = 164
thread 140282052859648 end working...
thread 140282052859648 start working...
thread 140282052859648 is working, number = 165
thread 140281810773760 end working...
thread 140281810773760 start working...
thread 140281810773760 is working, number = 166
thread 140282061252352 end working...
thread 140282061252352 start working...
thread 140282061252352 is working, number = 167
thread 140282036074240 end working...
thread 140282036074240 start working...
thread 140282044466944 end working...
thread 140282044466944 start working...
thread 140282044466944 is working, number = 169
thread 140282036074240 is working, number = 168
thread 140281819166464 end working...
thread 140281819166464 start working...
thread 140281819166464 is working, number = 170
thread 140281802381056 end working...
thread 140281802381056 start working...
thread 140281802381056 is working, number = 171
thread 140281777202944 start working...
thread 140281777202944 is working, number = 172
thread 140281793988352 end working...
thread 140281793988352 start working...
thread 140281793988352 is working, number = 173
thread 140281785595648 end working...
thread 140281785595648 start working...
thread 140281785595648 is working, number = 174
thread 140281810773760 end working...
thread 140281810773760 start working...
thread 140281810773760 is working, number = 175
thread 140282044466944 end working...
thread 140282061252352 end working...
thread 140282061252352 start working...
thread 140282061252352 is working, number = 177
thread 140282052859648 end working...
thread 140282052859648 start working...
thread 140282052859648 is working, number = 178
thread 140282044466944 start working...
thread 140282044466944 is working, number = 176
thread 140281819166464 end working...
thread 140282036074240 end working...
thread 140281819166464 start working...
thread 140281819166464 is working, number = 179
thread 140282036074240 start working...
thread 140282036074240 is working, number = 180
thread 140281802381056 end working...
thread 140281802381056 start working...
thread 140281802381056 is working, number = 181
thread 140281777202944 end working...
thread 140281777202944 start working...
thread 140281777202944 is working, number = 182
thread 140281793988352 end working...
thread 140281793988352 start working...
thread 140281793988352 is working, number = 183
thread 140281785595648 end working...
thread 140281785595648 start working...
thread 140281785595648 is working, number = 184
thread 140281810773760 end working...
thread 140281810773760 start working...
thread 140281810773760 is working, number = 185
thread 140281819166464 end working...
thread 140282044466944 end working...
thread 140282044466944 start working...
thread 140281819166464 start working...
thread 140282036074240 end working...
thread 140281819166464 is working, number = 187
thread 140282044466944 is working, number = 186
thread 140282061252352 end working...
thread 140282061252352 start working...
thread 140282061252352 is working, number = 188
thread 140282052859648 end working...
thread 140282036074240 start working...
thread 140281802381056 end working...
thread 140281802381056 start working...
thread 140281802381056 is working, number = 191
thread 140282052859648 start working...
thread 140282052859648 is working, number = 190
thread 140282036074240 is working, number = 189
thread 140281777202944 end working...
thread 140281777202944 start working...
thread 140281777202944 is working, number = 192
thread 140281802381056 end working...
thread 140281802381056 start working...
thread 140281802381056 is working, number = 193
thread 140281810773760 end working...
thread 140281810773760 start working...
thread 140281810773760 is working, number = 194
thread 140282052859648 end working...
thread 140282052859648 start working...
thread 140282052859648 is working, number = 195
thread 140282044466944 end working...
thread 140282044466944 start working...
thread 140282044466944 is working, number = 196
thread 140282061252352 end working...
thread 140282061252352 start working...
thread 140282061252352 is working, number = 197
thread 140282036074240 end working...
thread 140282036074240 start working...
thread 140282036074240 is working, number = 198
thread 140281785595648 end working...
thread 140281785595648 start working...
thread 140281785595648 is working, number = 199
thread 140281793988352 end working...
thread 140281819166464 end working...
thread 140281777202944 end working...
thread 140281802381056 end working...
thread 140282044466944 end working...
thread 140282061252352 end working...
thread 140282036074240 end working...
thread 140281785595648 end working...
thread 140281810773760 end working...
thread 140282052859648 end working...
threadExit() called, 140281793988352 exiting...
threadExit() called, 140281819166464 exiting...
threadExit() called, 140281777202944 exiting...
threadExit() called, 140281802381056 exiting...
threadExit() called, 140282061252352 exiting...
threadExit() called, 140282044466944 exiting...
threadExit() called, 140282036074240 exiting...
threadExit() called, 140281810773760 exiting...
threadExit() called, 140282052859648 exiting...
threadExit() called, 140281785595648 exiting...
heheda@heheda:~/Linux/threadpool$
gcc编译的时候出现错误,可以用core查看错误信息_呵呵哒( ̄▽ ̄)"的博客-CSDN博客