线程池用到的设计模式,线程池的使用场景
Yyds干货库存
线程池设计思路什么是线程池?先打个比方。线程池就像一个工具箱。每次需要拧螺丝的时候,我们都要从工具箱里拿出一把螺丝刀。有时候我们需要拿出一个来干。有时候螺丝太多的时候,我们需要不止一个人去拧。我们自己拧完螺丝,就把螺丝刀放回去,然后别人拿出来下次用。也许我的例子并不完美,但是我想我已经基本解释了线程池。说白了,线程池相当于提前申请了一些资源,也就是线程。必要时从线程池中取出线程来处理一些事情,处理完后再放回线程。
为什么我们需要线程池?我们来思考一个问题。为什么我们需要线程池?如果没有线程池,我们每次调用线程都是什么样子?很明显,第一步是创建一个线程,然后把任务交给线程,最后销毁线程。如果改用线程池,我们会在程序运行时先创建一批线程,然后交给线程池管理。必要时,我们取出线程来处理任务,不需要时,我们将它们放回线程池。这是否避免了每次创建和销毁线程的耗时操作?有人会说,你使用线程池的时候,一开始就消耗了一些内存,然后就再也不释放了。是不是有点浪费?其实这和空间换时间的概念差不多。我们确实多占用了一点内存,但是相对于我们珍惜的时间来说,这个内存是很划算的。
池的概念是一个非常常见的空间对时间的概念。除了线程池,还有进程池、内存池等等。其实他们的想法都是一样的,就是我先申请一批资源,然后我喜欢怎么拿就怎么拿,不用放回去。我这里听到的是不是有云计算的想法,都是一个道理。
如何设计线程池?现在硬核知识就要开始了。请坐好,抓紧扶手~
事不宜迟,我们来看看上图。我们要设计的线程池是什么样子的!
设计思路我们需要一个线程池类,那么线程池类中需要什么呢?让我们来看看吧。
我们需要存储我们创建的线程,所以我们需要一个容器来存储我们的任务。每次我们在这个容器里放一个任务,都是多线程的读任务,所以我们需要一个锁。每个读取任务都需要锁定和解锁。我们需要一个变量来判断什么时候终止,为了避免轮询而判断任务容器是否为空,效率太低。所以,我们这里用条件变量来解释什么是条件变量。条件变量是并发编程中的一种同步机制。条件变量使线程能够阻塞,直到某个条件发生,然后继续执行。在此期间,将首先释放之前获取的锁,而不影响其他人对该锁的访问。因此,条件变量是非常强大和高效的。(条件变量和锁将在我的多线程文章中详细解释。这不是重点,就不赘述了。)
接下来,我们来研究一下线程池中需要哪些操作。
将任务添加到线程池中,此时你要通知线程可以取任务执行循环操作,不断等待任务容器中的数据被执行,也就是初始化完成后需要做的事情可以通过改变终止变量来停止上述循环操作。至此,设计思路已经写的很清楚了,接下来就该看具体的实现了。
线程池的实现接下来,我们来看看线程池类是如何实现的。评论已经很详细了,直接加载代码就不多说了。
类CThreadMangerPool
{
公共:
CThreadMangerPool(void):is _ running(false){ };
bool init(int thread num);//初始化函数
~ CThreadMangerPool(void);
空跑(void);//执行函数
void stop(无效);//用于终止循环的函数
void add task(thread task * task);//将任务添加到任务容器的函数
私人:
bool CreateThreads(int thread num=5);
STD:vector STD:shared _ ptr STD:thread thread spool;//线程容器,用于存储线程
STD:list STD:shared _ ptr thread task thread task list;//任务容器,用于存储线程执行的任务。
STD:condition _ variable threadPool _ cv;//条件变量
std:互斥threadMutex//互斥体
//STD:vector STD:shared _ ptr CTcpClient TCP clients;
bool正在运行;//终止变量
};来几个关键的功能实现吧~
在Run函数中,我们设计了一个循环,不断执行等待,取出要执行的任务,如果没有要执行的任务,就休眠等待(通过前面提到的条件变量实现)。
注意,这里使用了一种技术。我们用while来判断任务容器中的数据是否为空,因为它类似于进程恐慌现象,这里存在条件变量的虚假唤醒。(这里不是重点,就不说了。我会在我文章的多线程处详细解释。)
void CThreadMangerPool:Run(){
std:shared_ptr ThreadTask任务;
While(true){ //处于循环中
std:unique_lock std:mutex守护(thread mutex);//使用RALL来管理锁,而不用手动释放它
while(thread task list . empty()){//这里防止了条件变量的假唤醒,所以不使用if判断。
如果(!is _ runing)
打破;
threadPool_cv.wait(警卫);//条件变量的使用
}
如果(!is _ runing)///以上是判断如果不启动或调用stop函数,循环会退出。
打破;
task=threadtasklist . front();//拿出任务
thread task list . pop _ front();//从容器中取出任务
if (task==NULL)
继续;
task-DoIt();//执行任务处理函数
task . reset();//重置指针
}
}接下来我们来看看添加任务的功能是如何实现的。
void CThreadMangerPool:add task(ThreadTask * task){
STD:shared _ ptr thread task ptr;//创建指向任务的智能指针
ptr.reset(任务);
{
std:lock_guard std:mutex防护(thread mutex);//锁也由RALL管理,避免了手动释放。
thread task list . push _ back(ptr);//向任务容器中添加任务
}
thread pool _ cv . notify _ all();//通知线程可以执行,即唤醒刚刚在条件变量处休眠的条件。
}好了,关键函数已经看完了,其他函数也很容易实现,包括初始化函数,终止函数等等。
撒完花~
这些代码来自我的后端框架Ratel,感兴趣的童鞋可以看看。
Github地址:https://github.com/hailong666/Ratel
过去回顾:
自制Ratel后端框架介绍
守护模块的设计思想
模块设计思想
配置文件模块的设计思想
,
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。