在计算机体系结构中有许多池式结构:内存池、数据库连接池、请求池、消息队列、对象池等等。
池式结构解决的主要问题为缓冲问题,起到的是缓冲区的作用。
- 解决任务处理
- 阻塞IO
- 解决线程创建于销毁的成本问题
- 管理线程
线程池应用之一:日志存储
在服务器保存日志至磁盘上时,性能往往压在磁盘读写上,而引入线程池利用异步解耦可以解决磁盘读写性能问题。
线程池的主要作用:异步解耦
说了那么多线程池的优点,那接下来要做的就是手撕这诱人的玩意了。
本文主要讲解的是c++线程池的实现,C语言实现其实思想和c++是一致的,具体的代码可见文章开头的链接。
若想自己编写一个线程池框架,那么可以先关注线程池中比较关键的东西:
- 工作队列
- 任务队列
- 线程池的池
- 中的回调函数
为什么说这些东西比较关键?因为这“大四元”基本上支撑起了整个线程池的框架。而线程池的框架如下所示:

如图所示,我们将整个框架以及任务添加接口定义为线程池的“池”,那么在这个池子中重要的就是工作队列、任务队列、以及决定工作队列中的到底应该工作还是休息的回调函数。
任务队列就简单得多了,想想编程语言中的任务应该是什么?不就是函数嘛。所以我们只需要定义一个函数该有的东西就行了。
对于一个线程池,任务队列和工作队列已经是必不可少的东西了,那线程池还有需要哪些东西辅助它以达到它该有的功能呢?
一说到线程,那处理好线程同步就是一个绕不开的话题,那在线程池中我们需要处理的临界资源有哪些呢?想想我们工作队列中的每个worker都在等待一个任务队列看其是否有任务到来,所以很容易得出结论我们必须要在线程池中实现两把锁:一把是用来控制对任务队列操作的互斥锁,另一把是当任务队列有新任务时唤醒worker的条件锁。
有了这两把锁,线程池中再加点必要的一些数字以及对线程池操作的函数,那么这个类就写完了。实现代码如下:
可以看到我们做了一些必要的封装,只给用户提供了构造函数、析构函数以及添加任务的函数。这也是一个基本的线程池框架必要的接口。
static?
注意到,此处的我们传入的回调函数必须是接受一个参数,且返回类型为的函数。如果我们将回调函数写成线程池的普通成员函数,那么c++会在这个函数参数前默认加上一个参数,这也是为什么我们能在成员函数中使用当前对象中的一些属性。然而就是这个原因,若我们传入的回调函数指针为类的成员函数,那c++编译器会破坏我们的函数结构(因为给我们加了一个形参),导致的第三个参数不符合要求而报错:

看吧,编译器不让我们用的成员函数作为回调函数传入中。其实在c++中,大多数回调函数都有这个要求。
那为什么就可以呢?
函数本身
在运行回调函数的时候,我们又想用对象里的东西(比如锁),编译器又不让我们用,那怎么办?别忘了虽然函数没有指针,但它却可以有一个的参数啊。有了这个,我们还怕少一个指针?我们可以先写一个函数,将需要的对象指针通过形参传到这个函数里,再在这个函数中通过这个对象调用成员函数的方法,就能使用这个对象的成员属性了。
就像这样:
至于的实现,由于线程是要一直存在的,一个的循环肯定少不了了。这个循环中具体做什么呢:不断检查任务队列中是否有job:
- 如果有,则取出这个job,并将该job从任务队列中删除,且执行job中的func函数。
- 如果没有,调用函数等待job到来时被唤醒。
- 若当前的为真,则退出循环结束线程。
注意在对job操作前别忘了加锁,函数实现如下:
至此,四大元的代码已经介绍完毕,接下来就是一些缝缝补补的工作:添加线程之类的。
添加任务
添加线程的逻辑其实也挺容易理解:传入一个job --> 尝试获取互斥锁 --> 将job添加到线程池的任务队列中 --> 释放锁 --> 通知worker来取线程,代码如下所示:
面向用户的添加任务
当然,我们不希望用户在使用我们的线程池的时候都需要自己定义job并添加到任务队列,job这种私密的关于内部实现的东西,我们也不希望用户能看到,所以我们可以封装一层面向用户的添加任务函数,一来可以方便线程池的使用者,二来也能隐藏内部实现:
构造函数
构造函数所做的工作就是根据用户传入的参数创建线程,并且初始化一些属性值。值得注意的是,我们最好在创建完线程后,调用函数,这样能让我们的worker都能安详得结束一切:
析构函数
析构函数无非就是做释放资源的事情,注意,由于我们detach了我们创造的线程,所以我们必须手动唤醒所有在条件等待的线程,并将worker的值置为:
这样,一个基本的线程池就被创建完毕了。
测试代码非常简单:使用线程池并发打印这0~999这1000个数:
测试代码:
运行结果:

性能测试
同样从0打印个数字,采用线程池的方式和普通的方式,由于不采用线程池需要等待所有线程执行完毕,所以必须使用函数。而使用线程池的计时方式则是从调用线程池构造函数到析构函数执行结束。
测试结果
不使用线程池

使用线程池

在源码链接中,用C语言实现的线程池代码里至今都保留着线程池的动态调整功能:
线程池放缩方案:
- 当空闲线程数量大于80%时,减少线程池中线程个数将空闲线程数量控制在50%。
- 当空闲线程数量小于40%时,增加线程池中线程个数并将空闲线程数量控制在50%。
此功能是不是画蛇添足有待考证,但是若当我们的工作线程数量不稳定时,频繁调整线程池的线程数量(频繁创建及释放线程)也将导致性能的下降,这种做法理所当然地被我认为是一种本末倒置的做法,而对于线程池中线程的数量,在我做性能测试时考虑到CPU是四核的,就只在线程池中创建了4个线程。关于线程池中线程合理的数量,大家可自行网上查找相关研究,笔者在此给出大部分的结论:
最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目
版权声明:
本文来源网络,所有图片文章版权属于原作者,如有侵权,联系删除。
本文网址:https://www.mushiming.com/mjsbk/4860.html