参考博客:https://blog.csdn.net/sjsjnsjnn/article/details/126364511
一、什么是线程池
-
线程池是线程的一种使用模式。在前面的情况中,我们都是遇到任务然后创建线程再执行。但是线程的频繁创建就类似于内存的频繁申请,会给操作系统带来更大的压力,进而影响整体的性能。
-
所以我们一次申请好一定数量而定线程,然后将线程的管理操作交给线程池,就避免了在短时间内不断创建与销毁线程的代价,线程池不但能够保证内核的充分利用,还能防止过分调度,并根据实际业务情况进行修改。
二、线程池的优点
- 任务来到立马就有线程去执行任务,节省了创建线程的时间。
- 防止服务器线程过多导致的系统过载问题
- 相对于进程池,线程池资源占用较少,但是健壮性很差
三、线程池的应用
需要大量的线程来完成任务,且完成任务的时间比较短
- 例如:WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。
对性能要求苛刻的应用
- 比如要求服务器迅速响应客户请求。
接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用
- 突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误。
四、实现一个简单的线程池
线程池中提供了一个任务队列,以及若干个线程。示意图如下:
我们创建好了线程池之后,首次我们先是对其进行初始化操作;然后不断的向任务队列塞数据,由线程池中的线程去获取任务并执行相关操作;
-
任务队列(即临界资源)是会被多个执行流同时访问,因此我们需要引入互斥锁对任务队列进行保护。
-
线程池中的线程想要获取到任务队列中的任务,那么就必须要确保任务队列中有任务,所以我们还需引入条件变量来进行判断,如果队列中没有任务,线程池中的线程将会被挂起,直到任务队列中有任务后才被唤醒;
-
在thread_pool.hpp中,多线程去执行对应的方法的时候,采用的是静态成员函数,这样做的目的是解决类中存在隐藏的this指针问题,因为多线程在调用对应的函数时,该函数只有一个形参,不加static的话,那么形参个数就有两个,是不可以的;所以我们可以将this指针作为参数传递过去,就可以访问类内的成员函数了;
下面是完整的代码实现
threadpool.hpp
- 这个文件封装了线程池,使用模板的
template
的方式,创建了一个线程池 - 初始化线程池的时候,所有的线程都设置了
detach
模式,这样最后不需要手动join
线程,我们不关心线程池关闭之后所有线程之间的情况 - 任务对象需要有
Run
的接口
#pragma once
#include<iostream>
#include<string>
#include<queue>
#include<unistd.h>
#include<pthread.h>
template<typename T>
class ThreadPool
{
public:
void Lock(){ pthread_mutex_lock(&mutex); }
void Unlock(){ pthread_mutex_unlock(&mutex); }
bool IsEmpty(){ return task_queue.empty();}
void Wait(){ pthread_cond_wait(&cond,&mutex); }
void WakeUp(){ pthread_cond_signal(&cond); }
void PushTask(const T& task){
Lock();
task_queue.push(task);
Unlock();
WakeUp();
}
void PopTask(T* outTask){
*outTask = task_queue.front();
task_queue.pop();
}
public:
ThreadPool(int num_ = 8):num(num_)
{
pthread_mutex_init(&mutex,NULL);
pthread_cond_init(&cond,NULL);
}
static void* Routine(void* args){ //线程执行函数
pthread_detach(pthread_self());
ThreadPool<T>* tp = (ThreadPool<T>*)(args);
while(true){
tp->Lock();
while(tp->IsEmpty()){
tp->Wait();
}
T t;
tp->PopTask(&t);
tp->Unlock();
t.Run(); //自定义的Run接口
}
}
void initThreadPool(){
pthread_t tid;
for(int i = 0 ; i < num ; ++i){
pthread_create(&tid,NULL,Routine,(void*)this);
}
}
~ThreadPool(){
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
}
private:
int num; //线程数量
std::queue<T>task_queue;
pthread_mutex_t mutex;
pthread_cond_t cond;
};
task.hpp
- 这个文件封装的一个任务对象,有
Run
接口,用于适配线程池 - 我们使用数学计算来模拟任务,实际情况可能复杂的多
#pragma once
#include <iostream>
#include <pthread.h>
class Task
{
public:
Task() {}
Task(int x_, int y_, char op_) : x(x_), y(y_), op(op_) {}
int Run()
{
int ret = 0;
switch (op)
{
case '+':
ret = x + y;
break;
case '-':
ret = x - y;
break;
case '*':
ret = x * y;
break;
case '/':
ret = x / y;
break;
default:
break;
}
std::cout << "thread id = " << pthread_self() << ",result:" << x << op << y << "=" << ret << std::endl;
return ret;
}
int operator()(){ return Run(); }
~Task(){}
private:
int x;
int y;
char op;
};
main.cpp
- 在
main
函数中使用这个线程池,并且持续加入任务
#include"threadpool.hpp"
#include"task.hpp"
char operations[4] = {'+','-','*','/'};
int main(){
ThreadPool<Task>* threadPool = new ThreadPool<Task>(8);
threadPool->initThreadPool();
srand(time(NULL));
while(true){
int x = rand() % 100;
int y = rand() % 100;
char op = operations[rand() % 4];
Task task(x,y,op);
threadPool->PushTask(task);
sleep(1);
}
return 0;
}
Makefile
- 编写
Makefile
编译项目
CXX = g++
CXXFLAGS = -Wall -std=c++14
SRCS = main.cpp
OBJS = $(SRCS:.cpp=.o)
TARGET = threadPool
all:$(TARGET)
$(TARGET):$(OBJS)
$(CXX) $(CXXFLAGS) -o $(TARGET) $(OBJS)
%.o:%.cpp
$(CXX) $(CXXFLAGS) -c $< -o $@
clean:
rm -f $(OBJS) $(TARGET)
.PHONY:all clean
运行结果
- 可以发现,持续有线程从任务队列中获取任务并执行
更多资料:https://github.com/0voice