
📄前言
不知道各位是否有试过点进限时抽奖网站、抢票网站呢?你是否好奇过一个网站、游戏是如何实现数十万、百万用户一起进行访问呢? 其实这类软件的背后的服务器总是离不开一个叫做线程池的设计,而这就是本文将讲解的内容,学习如何设计线程池,是每个后端程序猿的必修课之一。
线程池的原理
概念
当我们在一台多核CPU机器上使用多线程,无疑能够提高程序的性能,但不是每台机器的CPU都是“线程撕裂者”,我们无法给每个任务都分配一条线程,过度分配线程只会让程序性能下降。为此,我们需要给线程与需要执行的任务进行集中管理,减少线程创建和销毁所造成的开销,而这就是线程池。
线程池也是池化技术的一种,即将资源提前分配好,集中管理,当需要使用的时候在去使用,从而提高程序的效率。
-
线程池的优点:
- 提高程序可维护性:线程池作为一个模块,降低了代码的耦合性,使得程序的可维护性得到提高
- 减少系统开销:避免频繁地创建和销毁资源,减少了系统的开销,尤其是在高负载情况下更能体现出其效率。
- 提高性能:通过并发执行任务,从而提高程序的性能。
工作原理
线程池最基础的三个结构组成就是任务队列、工作线程、线程管理。其中线程池中的任务队列负责将用户的任务打包成合适的格式,等待工作线程来取走。工作线程负责任务的并发执行,而线程管理则用于管理线程的创建、消亡。
其实,线程池也可以看作是一种消费者生产者模型。
- 生产者:用户负责生成任务并将它们交给任务队列。可以将这看作为发送一个资源请求。
- 消费者:线程池负责消费者的角色,用于处理将任务队列中的任务(资源)。
介绍完了线程池的工作原理,那就简单的介绍下工作流程吧。
工作流程:
+----------------+ +---------------------+ +-------------------+ +-------------------+
| 线程池初始化 | | 添加任务到任务队列 | | 线程从队列中取任务 | | 执行任务并返回线程池 |
| 创建线程 | --> | 所有任务入队列 | --> | 自动取出并执行任务 | --> | 线程等待下一任务 |
+----------------+ +---------------------+ +-------------------+ +-------------------+
线程池的实现
要实现一个不那么塑料的线程池,就得要对C++的包装器、异步函数工具、完美转发有所了解,如果你对它们不太了解,可以去观看我的上一篇文章。
你知道如何快速实现一个简单、高效的线程池吗?那就是上github搜索线程池,然后找到一个最多人收藏的(✓)。本篇文章讲解的线程池是基于github上大佬的线程池 所修改的。如果要提高编程实力,请务必去多上github观看源码。
线程池的基础结构
这个线程池只实现了线程池所必备的三个功能,**任务队列、工作线程、线程管理。**这个线程池的实现不过百行,对于初学者来说比较友好,没有必要把焦点分散到其他地方,只需要集中于了解线程池本身的实现原理。
class ThreadPool {
public:
static ThreadPool* getInstance(size_t n = 0); //单例模式实现
template<class F, class... Args>
auto enqueue(F&& f, Args&&... args) //将任务推入队列
-> std::future<typename std::invoke_result_t<F, Args...>>; //c++17 invoke_result_t用于获取推导的函数返回值
template<class F> // 无参函数版本
auto enqueue(F&& f)
-> std::future<typename std::invoke_result_t<F>>;
~ThreadPool();
ThreadPool(const ThreadPool&) =