线程池的实现
在上次我实现了线程池的一个组件:任务队列。这次来实现线程池。
线程池的概念
线程池其实就是一种多线程处理形式,处理过程中可以将任务添加到队列中,然后在创建线程后自动启动这些任务。
为什么要用线程池
直接创建销毁线程的缺点
-
我们使用线程的目的本是出于效率考虑,可以为了创建这些线程却消耗了额外的时间,资源,对于线程的销毁同样需要系统资源。
-
cpu资源有限,上述代码创建线程过多,造成有的任务不能即时完成,响应时间过长。
-
线程无法管理,无节制地创建线程对于有限的资源来说似乎成了“得不偿失”的一种作用。
线程池的好处
-
降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
-
提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
-
提高线程的可管理性。
书接上回
c++实现一个高并发服务器(一)
线程池的组件
成员 | 作用 |
---|---|
ThreadPool(int minNum,int maxNum) | 输入最小和最大线程数来创建一个线程池 |
~ThreadPool() | 析构函数。回收管理员线程和所有存活的工作线程,销毁互斥锁和条件变量。 |
void addTask() | 添加任务的接口。 |
int getBusyNum() | 获得工作中的线程的数量。 |
int getAliveNum(); | 获得存活的线程的数量。 |
static void* worker(void* arg) | 工作线程的执行函数。 |
static void* manager(void* arg) | 管理员线程的执行函数。负责动态的添加或删除工作线程。 |
void ThreadExit() | 线程退出函数。 |
TaskQueue* m_tq | 任务队列,存放待执行任务 |
pthread_mutex_t m_mutex | 互斥锁,保证线程同步 |
pthread_cond_t m_not_empty | 条件变量,任务队列为空时工作线程阻塞并等待此条件变量,用于在要回收工作线程和有添加新任务时发出信号唤醒线程。 |
pthread_t* m_pworkerThreadId | 工作线程的指针,用于申请动态内存存放一组工作线程。 |
pthread_t m_managerThreadId | 管理员线程 |
int m_minNum | 线程池最小线程数量 |
int m_maxNum | 线程池最大线程数量 |
int m_busyNum | 线程池中执行任务的工作线程的数量 |
int m_aliveNum | 线程池中存活的工作线程的数量 |
int m_exitNum | 线程池中等待退出的工作线程的数量。 |
bool m_shutdown=false | 线程池关闭标志 |
关键部分讲解
-
管理员线程:管理员线程在线程池存在时就一直检测线程池(每秒检测一次),在检测期间做如下任务:
- 加锁获取线程池的执行任务线程数量和任务数
- 添加线程策略/算法(当前任务个数>存活的线程数 && 存活的线程数<最大线程个数),线程池加锁,然后向线程池中加工作线程
- 销毁多余的线程(忙线程*2 < 存活的线程数目 && 存活的线程数 > 最小线程数量)
-
工作线程:在线程池创建时被创建,用一个线程数组保存,没有任务时阻塞,有任务时取出任务并执行。
- 访问任务队列(共享资源)时加锁
- 判断任务队列是否为空,如果为空则工作线程阻塞(等待条件变量),解除阻塞(收到条件变量,但有可能是要求销毁线程)先判断是否要销毁线程(如果待退出的线程数大于0则需要退出线程,另外如果线程池关闭标志为真也需要退出),然后从任务队列中取出一个任务并执行。循环此过程。
-
线程回收
- 在线程池关闭或管理员线程销毁多于线程时执行
- 调用threadExit(pthread_self())
-
线程池创建
- 实例化任务队列,初始化各成员变量
- 根据线程池最大数量给线程数组分配内存
- 初始化条件变量和互斥锁
- 创建工作线程和管理员线程
-
线程池析构
- 将线程池关闭标志置为真,先销毁管理员线程
- 再唤醒所有工作线程让他们自己结束。
- 销毁条件变量、互斥锁
代码实现
ThreadPool.h
#pragma once
#include"TaskQueue.h"
class ThreadPool{
public:
ThreadPool(int minNum,int maxNum);
~ThreadPool();
void addTask(Task &task);
int getBusyNum();
int getAliveNum();
private:
static void* worker(void* arg);
static void* manager(void* arg);
void ThreadExit();
TaskQueue* m_tq;
pthread_mutex_t m_mutex;
pthread_cond_t m_not_empty;
pthread_t* m_pworkerThreadId;
pthread_t m_managerThreadId;
int m_minNum;
int m_maxNum;
int m_busyNum;
int m_aliveNum;
int m_exitNum;
bool m_shutdown=false;
};
ThreadPool.cpp
#include"ThreadPool.h"
#include<iostream>
#include<string.h>
#include<unistd.h>
#include<string>
using namespace std;
ThreadPool::ThreadPool(int minNum,int maxNum) {
m_tq=new TaskQueue;
do{
m_minNum = minNum;
m_maxNum = maxNum;
m_busyNum=0;
m_aliveNum=minNum;
m_exitNum=0;
m_pworkerThreadId=new pthread_t[maxNum];
if(m_pworkerThreadId==nullptr){
cout<<"malloc thread_t[] failed..."<<endl;
break;
}
memset(m_pworkerThreadId,0,sizeof(pthread_t)*maxNum);
if(pthread_mutex_init(&m_mutex,NULL)!=0||pthread_cond_init(&m_not_empty,NULL)!=0){
cout<<"init mutex or condition failed"<<endl;
break;
}
//create threads
for(int i=0;i<minNum;i++){
cout<<"create thread..."<<endl;
pthread_create(&m_pworkerThreadId[i],NULL,worker,this);
}
pthread_create(&m_managerThreadId,NULL,manager,this);
}while(0);
}
ThreadPool::~ThreadPool(){
m_shutdown=1;
pthread_join(m_managerThreadId,NULL);
cout<<"manager thread is destroyed"<<endl;
for(int i=0;i<m_aliveNum;i++){
pthread_cond_signal(&m_not_empty);
}
cout<<"worker thread is destroyed"<<endl;
if(m_tq)delete m_tq;
if(m_pworkerThreadId)delete m_pworkerThreadId;;
pthread_mutex_destroy(&m_mutex);
pthread_cond_destroy(&m_not_empty);
cout<<"thread pool destroyed"<<endl;
}
void ThreadPool::addTask(Task &task){
if(m_shutdown){
return;
}
cout<<"Add task to pool"<<endl;
m_tq->addTask(task);
pthread_cond_signal(&m_not_empty);
}
int ThreadPool::getAliveNum(){
int threadNum = 0;
pthread_mutex_lock(&m_mutex);
threadNum=m_aliveNum;
pthread_mutex_unlock(&m_mutex);
return threadNum;
}
int ThreadPool::getBusyNum(){
int busyNum =0;
pthread_mutex_lock(&m_mutex);
busyNum=m_busyNum;
pthread_mutex_unlock(&m_mutex);
return busyNum;
}
void* ThreadPool::worker(void* arg){
ThreadPool* pool=static_cast<ThreadPool*>(arg);
while(true){
pthread_mutex_lock(&pool->m_mutex);
while(pool->m_tq->size()==0&&!pool->m_shutdown){
cout<<"worker thread"<<to_string(pthread_self())<<"waiting..."<<endl;
pthread_cond_wait(&pool->m_not_empty,&pool->m_mutex);
if(pool->m_exitNum>0){
//there are some thread need to be destroied
pool->m_aliveNum--;
pthread_mutex_unlock(&pool->m_mutex);
pool->ThreadExit();
}
}
if(pool->m_shutdown){
pthread_mutex_unlock(&pool->m_mutex);
pool->ThreadExit();
}
Task task=pool->m_tq->takeTask();
pool->m_busyNum++;
pthread_mutex_unlock(&pool->m_mutex);
cout<<"worker thread "<<to_string(pthread_self())<<"start working..."<<endl;
task.function(task.arg);
cout<<"worker thread"<<to_string(pthread_self())<<"end working..."<<endl;
pthread_mutex_lock(&pool->m_mutex);
pool->m_busyNum--;
pthread_mutex_unlock(&pool->m_mutex);
}
return nullptr;
}
void* ThreadPool::manager(void* arg){
ThreadPool *pool=static_cast<ThreadPool*>(arg);
while(!pool->m_shutdown){
sleep(1);
pthread_mutex_lock(&pool->m_mutex);
int queueSize=pool->m_tq->size();
int liveNum=pool->m_aliveNum;
int busyNum=pool->m_busyNum;
pthread_mutex_unlock(&pool->m_mutex);
const int NUMBER=2;//add 2 worker thread at once
if(queueSize>liveNum&&liveNum<pool->m_maxNum){
pthread_mutex_lock(&pool->m_mutex);
int num=0;
for(int i=0;i<pool->m_maxNum&&num<NUMBER;++i){
if(pool->m_pworkerThreadId[i]==0){
pthread_create(&pool->m_pworkerThreadId[i],NULL,worker,pool);
num++;
pool->m_aliveNum++;
}
}
pthread_mutex_unlock(&pool->m_mutex);
}
//destroy rest of threads
if(busyNum*2<liveNum&&liveNum>pool->m_minNum){
pthread_mutex_lock(&pool->m_mutex);
pool->m_exitNum=NUMBER;
pthread_mutex_unlock(&pool->m_mutex);
for(int i=0;i<NUMBER;i++){
pthread_cond_signal(&pool->m_not_empty);
}
}
}
return nullptr;
}
void ThreadPool::ThreadExit(){
pthread_t tid=pthread_self();
for(int i=0;i<m_maxNum;i++){
if(m_pworkerThreadId[i]==tid){
cout<<"ThreadExit() :thread "<<to_string(pthread_self())<<"exiting..."<<endl;
m_pworkerThreadId[i]=0;
break;
}
}
pthread_exit(NULL);
}