前言
在现代计算机编程领域,随着多核处理器和分布式系统的广泛应用,并发编程变得越来越重要。而线程作为实现并发的基本手段之一,其重要性不言而喻。然而,在实际的应用开发中,如何高效地管理和调度线程,以充分利用系统资源并提高程序的性能,一直是一个具有挑战性的问题。
线程池作为一种被广泛应用的解决方案,为解决这一难题提供了一种优雅而有效的方法。它通过预先创建一定数量的线程,并合理地分配和管理任务,避免了频繁创建和销毁线程所带来的开销,同时也能更好地控制和协调线程的执行,从而显著提高程序的并发性能。
本文将深入探讨线程池的概念、原理、应用场景以及实现细节,帮助读者全面了解线程池的相关知识,并能够在实际项目中灵活运用线程池来优化程序性能。
1.池化技术
池化技术是一种资源管理策略,它通过预先创建和缓存一定数量的资源(如数据库连接、线程、对象等),并在需要时从池中获取资源,使用完毕后将资源放回池中,以便下次复用。
池化技术的目的是提高资源的利用率,减少资源的创建和销毁开销,从而提高系统的性能和效率。
常见的池化类型:线程池、对象池等
2.线程池
线程池是池化技术的一种具体应用,是一种管理和复用线程的机制。
在传统的编程模式中,每当有任务需要执行时,就会创建一个新的线程来处理该任务。当任务完成后,线程就会被销毁。
频繁地创建和销毁线程会带来较大的开销,包括系统资源的分配和回收、线程上下文的切换等。
线程池则预先创建了一定数量的线程,并将它们维护在一个池子中。
当有任务到来时,从线程池中获取一个空闲线程来执行任务;当任务完成后,线程不会被销毁,而是放回线程池中,等待下一次任务的分配。
2.1线程池的工作流程
线程池的工作流程可以归纳为以下四个步骤
任务提交
线程分配
执行任务
线程回收
1.任务提交
- 当有新的任务需要执行时,应用程序将任务提交给线程池。线程池会根据当前线程池的状态和配置策略来决定如何处理该任务。
2.线程分配
- 如果线程池中有空闲线程,那么直接将任务分配给该线程执行
- 如果没有空闲线程,且当前线程数量未达到线程池的最大限制,那么会创建一个新的线程来执行任务
- 如果线程数量已经达到最大限制,且任务队列已满,那么根据线程池的拒绝策略来处理该任务,常见的拒绝策略包括直接丢弃任务、抛出异常等
3.任务执行
- 线程从线程池中获取到任务后,开始执行任务。在执行过程中,线程池会对线程进行监控和管理,确保任务的正常执行
4.线程回收
- 当任务执行完成后,线程不会被销毁,而是放回线程池中,等待下一次任务的分配
3.标准线程池的实现
了解了什么是线程池,现在就来实现一下线程池
首先需要一个线程类,该类包含线程的基本信息
- 线程tid
- 线程数据
- 执行函数
以下是Thread类的具体实现
thread.hpp
struct ThreadData{
void* _args;
std::string _name;
ThreadData(std::string& name,void* args)
:_name(name)
,_args(args)
{}
};
typedef void* (*fun_t)(void*);//线程要执行的函数的参数和返回值都为void*
class Thread{
private:
fun_t func; // 线程要执行的函数
ThreadData _data; // 线程的名字和线程的数据
pthread_t tid; // 线程的tid
public:
Thread(std::string& name,fun_t function,void * args)
:func(function)
,_data(name,args)
{}
//启动一个线程
void start(){
pthread_create(&tid,nullptr,func,_data._args);
}
//等待指定线程
void join(){
pthread_join(tid,nullptr);
}
std::string& name(){
return _data._name;
}
~Thread(){}
};
线程池中会将任务放入任务队列当中,每个线程都会从任务队列中获取任务
多线程并发访问同一个任务队列,任务队列是共享资源,因此需要锁来保护任务队列
我们平常使用锁对资源进行保护,需要两个操作
- 加锁——pthread_mutex_lock
- 解锁——pthread_mutex_unlock
每次都需要两行语句,很麻烦
有没有什么简单、不吃操作的办法
很简单,实现一个LockGuard类,让其实现自动加锁、解锁
- 创建这个类对象时,自动加锁
- 析构这个类对象时,自动解锁
以下是LockGuard类的具体实现
LockGuard.hpp
class Mutex{
private:
pthread_mutex_t* _mtx;
public:
Mutex(pthread_mutex_t* mtx):_mtx(mtx){}
void lock(){
pthread_mutex_lock(_mtx);
}
void unlock(){
pthread_mutex_unlock(_mtx);
}
~Mutex(){}
};
//构造时加锁,
//出作用域时,析构时解锁
//一键实现自动加锁和解锁
class LockGuard{
private:
Mutex _mtx;
public:
LockGuard(pthread_mutex_t* mtx):_mtx(mtx){
_mtx.lock();
}
~LockGuard(){
_mtx.unlock();
}
};
准备好了线程类和LockGuard类,则就是线程池类的实现
实现线程池,紧扣以下要求即可
- 提交任务
- 线程获取任务
盯着这两点,再进行扩展。
以下是线程池类的实现
ThreadPool.hpp
template<class T>
class ThreadPool{
private:
std::vector<Thread*> _threads;//用于存储找到所有线程的表
int _num;//线程的数量
std::queue<T> _task_queue;//任务队列
static ThreadPool<T>* _pool_ptr;//线程池管理句柄,一种线程池只能出现一次
static pthread_mutex_t _mtx;
pthread_mutex_t lock;
pthread_cond_t cond;
bool stop=false;
public:
pthread_mutex_t* getMutex(){
return &lock;
}
bool isEmpty(){
return _task_queue.empty();
}
void waitCond(){
pthread_cond_wait(&cond,&lock);
}
T getTask(){
T task = _task_queue.front();
_task_queue.pop();
return task;
}
private:
ThreadPool(int num):_num(num){
pthread_mutex_init(&lock,nullptr);
pthread_cond_init(&cond,nullptr);
for(int i=1;i<=_num;i++){
std::string name="Thread"+std::to_string(i);
_threads.push_back(new Thread(name,runtine,this));
}
}
public:
~ThreadPool(){
stop = true;
pthread_cond_broadcast(&cond);
for(auto& x: _threads){
x->join();
delete x;
}
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&cond);
}
//单例化模式
static ThreadPool<T>* getThreadPoolPtr(int num){
if(_pool_ptr == nullptr){
LockGuard tmp(&_mtx);
if(!_pool_ptr){
_pool_ptr = new ThreadPool<T>(num);
}
}
return _pool_ptr;
}
void push_task(const T& task){
LockGuard tmp(&lock);//提交任务
_task_queue.push(task);
pthread_cond_signal(&cond);//唤醒一个线程
}
void run(){
for(auto& x: _threads){
x->start();
std::cout<< x->name()<<"启动成功"<<std::endl;
}
}
static void* runtine(void* arg){
ThreadData* data = (ThreadData* ) arg;
ThreadPool<T>* ptr = (ThreadPool<T>*)data->_args;
while(true){
T task;
{
LockGuard tmp(ptr->getMutex());
while (ptr->isEmpty())
{ if(stop) return nullptr;
ptr->waitCond();
}
if(stop) return nullptr;
task = ptr->getTask();
//执行任务
}
}
}
};
template<typename T>
ThreadPool<T>* ThreadPool<T>::_pool_ptr = nullptr;
template<typename T>
pthread_mutex_t ThreadPool<T>::_mtx = PTHREAD_MUTEX_INITIALIZER;
4.简易线程池
上面的标准线程池实现起来是很麻烦的,且细节会更多
当有人让你现场手撕的时候,你不一定能写出来,就算写出来,也会浪费很多时间
能展现你的实力,且不会占用太多时间,我们实现一个简易的线程池即可
简易线程池的实现
class ThreadPool{
private:
int _num; // 线程数量
static ThreadPool<task>* _ptr;
std::vector<int> threads;
static pthread_mutex_t* mutex;
pthread_mutex_t *_mtx;
pthread_cond_t *_cond;
std::queue<tsak> _tasks;
private:
ThreadPoll(int num) : _num(num)
{
pthread_mutex_init(_mtx);
pthread_cond_init(_cond);
}
ThreadPool(ThreadPool &another) = delete;
ThreadPool &operator=(ThreadPool &another) = delete;
public:
ThreadPool<task>* getThreadPoolPtr(int num){
if(_ptr == nullptr){
pthread_mutex_lock(mutex);
if(_ptr == nullptr){
_ptr = new ThreadPoll<task>(num);
}
pthread_mutrx_unlock(mutex);
}
return _ptr;
}
void push(task value){
pthread_mutex_lock(_mtx);
_tasks.push(value);
pthread_mutex_unlock(_mtx);
pthread_cond_signal(_cond);
}
bool isEmpty(){
return _tasks.size()==0?true:false;
}
void getTask(task& t){
t = queue.front();
queue.pop();
}
void* runFunction(void* arg){
ThreadPoll<task>* ptr = (ThreadPoll<task>* )arg;
while(true){
task T;
pthread_mutex_lock(_mtx);
while(ptr->isEmpty()){
pthread_cond_wait(_cond,_mtx);
}
ptr->getTask(T);
pthread_mutex_unlock(_mtx);
//
}
}
void start(){
for(int i=0;i<_num;i++){
pthread_t tid = 0;
pthread_create(&tid,nullptr,runFunction,(void*)this);
threads.push_back(tid);
}
}
};
template< class task>
ThreadPool<task>* ThreadPool<task>::_ptr = nullptr;
template< class task>
pthread_mutex_t* ThreadPool<task>::mutex = PTHREAD_MUTEX_INITIALIZER;
结语
在本文中,我们一同探索了线程池的奇妙世界,了解了它的工作原理、优点以及具体的实现方法。线程池就像是一个神奇的“工厂”,高效地管理和调度着线程资源,让它们有条不紊地完成各项任务。
在结束这份探索之旅时,你可能会思考一些更深层次的问题。比如,如何根据不同的业务需求选择最适合的线程池参数?在高并发环境下,如何进一步优化线程池的性能?这些问题都需要我们在实践中不断探索和总结。希望大家在今后的学习和工作中,能够继续深入研究线程池,将其运用到更多的场景中,为解决实际问题提供更多的思路和方法。