【c++】线程池的原理及实现

在这里插入图片描述

💻文章目录

  • 📄前言
  • 线程池的原理
    • 概念
    • 工作原理
  • 线程池的实现
    • 线程池的基础结构
    • 任务队列的实现
    • 工作线程的实现
  • 线程池的应用与拓展
    • 线程池的拓展
  • 📓总结


📄前言

不知道各位是否有试过点进限时抽奖网站、抢票网站呢?你是否好奇过一个网站、游戏是如何实现数十万、百万用户一起进行访问呢? 其实这类软件的背后的服务器总是离不开一个叫做线程池的设计,而这就是本文将讲解的内容,学习如何设计线程池,是每个后端程序猿的必修课之一。

线程池的原理

概念

当我们在一台多核CPU机器上使用多线程,无疑能够提高程序的性能,但不是每台机器的CPU都是“线程撕裂者”,我们无法给每个任务都分配一条线程,过度分配线程只会让程序性能下降。为此,我们需要给线程与需要执行的任务进行集中管理,减少线程创建和销毁所造成的开销,而这就是线程池。

线程池也是池化技术的一种,即将资源提前分配好,集中管理,当需要使用的时候在去使用,从而提高程序的效率。

  • 线程池的优点:

    1. 提高程序可维护性:线程池作为一个模块,降低了代码的耦合性,使得程序的可维护性得到提高
    2. 减少系统开销:避免频繁地创建和销毁资源,减少了系统的开销,尤其是在高负载情况下更能体现出其效率。
    3. 提高性能:通过并发执行任务,从而提高程序的性能。

工作原理

线程池最基础的三个结构组成就是任务队列工作线程线程管理。其中线程池中的任务队列负责将用户的任务打包成合适的格式,等待工作线程来取走工作线程负责任务的并发执行,而线程管理则用于管理线程的创建、消亡。

其实,线程池也可以看作是一种消费者生产者模型

  • 生产者:用户负责生成任务并将它们交给任务队列。可以将这看作为发送一个资源请求。
  • 消费者:线程池负责消费者的角色,用于处理将任务队列中的任务(资源)。

请添加图片描述

介绍完了线程池的工作原理,那就简单的介绍下工作流程吧。

工作流程:

+----------------+       +---------------------+       +-------------------+       +-------------------+
| 线程池初始化     |       | 添加任务到任务队列    |       | 线程从队列中取任务  |       | 执行任务并返回线程池  |
| 创建线程         |  -->  | 所有任务入队列        |  -->  | 自动取出并执行任务   |  -->  | 线程等待下一任务       |
+----------------+       +---------------------+       +-------------------+       +-------------------+

线程池的实现

要实现一个不那么塑料的线程池,就得要对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&) = delete;	//防止拷贝
    ThreadPool& operator=(const ThreadPool&) = delete;
private:
    ThreadPool(size_t);

private:
    std::once_flag _flag;   //用于初始化函数

    std::vector< std::thread > _workers;    // 工作线程
    std::queue< std::function<void()> > _tasks; // 任务队列
    std::mutex _queue_mutex;    // 用于保护任务队列的互斥锁
    std::condition_variable _condition; // 用于同步操作
    std::atomic<bool> _stop;
};

任务队列的实现

任务队列的难点在与如何理解如何包装任务的,以及智能指针的理解。但抛开C++中这些繁杂的部分,其实也就是往队列中发送任务。

template<class F, class... Args>
auto enqueue(F&& f, Args&&... args) //将任务推入队列
-> std::future<std::invoke_result_t<F, Args...>>	//返回一个期待,用于获取函数运行结果/异常结果
{
    using result_type = std::invoke_result_t<F, Args...>;

    auto task = std::make_shared< std::packaged_task<result_type()> > (
            std::bind(std::forward<F>(f), std::forward<Args>(args)...)
            );  // 包装任务成为异步任务。使用shared_ptr来动态管理存亡。

    std::future<result_type> res = task->get_future();  //期待绑定task
    {
        std::lock_guard<std::mutex> lock(_queue_mutex); // 保护任务队列,
        if(_stop)   throw std::runtime_error("在停止的线程池中加入任务");
        _tasks.emplace([task] { (*task)(); });  //再次将任务包装成 function<void()>。
    }
    _condition.notify_one();    //通知线程有任务可以消费了
    return res;
}

// 尝试自己去实现无参函数版本

工作线程的实现

工作线程的实现相比任务队列而言较为简单,只要确保往任务队列中取数据是线程安全,以及正确地结束线程即可。

static ThreadPool* getInstance(size_t n = 0)   //单例模式实现
{   //c++11 的call_once,可用于保证线程安全地初始化单例对象,从而杜绝双重检查语句。
    std::call_once(_flag, [n] { _instance = new ThreadPool(n); });
    return _instance;
}

ThreadPool(size_t n)
{
    for(int i = 0; i < n; ++i)
    {
        _workers.emplace_back
        (
            [this]
            {
                for(;;)
                {
                    std::function<void()> task;
                    {
                        std::unique_lock<std::mutex> lock(_queue_mutex);    //保护任务队列
                        //当队列为空时,等待新任务
                        _condition.wait(lock, [this]{ return _stop || !_tasks.empty(); });
                        if(_stop && _tasks.empty()) return; // 线程池停止,且没有任务,则退出
                        task = std::move(_tasks.front()); // 从队列获取任务
                        _tasks.pop();
                    }
                    task(); // 并发执行任务
                }
            }
        );
    }
}

~ThreadPool()
{
    _stop.store(true);	
    for(auto& it : _workers)	//回收线程资源
        it.join(); 	
}

std::once_flag ThreadPool::_flag;	//静态成员要在类外初始化
ThreadPool* ThreadPool::_instance = nullptr;

线程池的应用与拓展

线程池的设计就至此结束了,让我们来简单使用下做好的线程池吧。

int test(int num1, int num2)
{
    return num1 + num2;
}

int main() {
    httplib::Server server;
    ThreadPool* pool = ThreadPool::getInstance(5);
    std::vector<std::future<int>> results(10);

    for (int i = 0; i < 10; i++) {
        results[i] = pool->enqueue(test, i, i + 1);
    }

    for(auto& it : results)
        std::cout << it.get() << std::endl;

    return 0;
}

线程池的拓展

这个线程池只是一个非常基础的线程池实现,实际的线程池会比这复杂得多,如下为线程池常见的拓展方式:

  1. 实现动态调整线程数:针对不同的场景线程池能够自动释放、创建线程。
  2. 优先级任务队列:为了确保紧急任务能够快速执行,可以定义一个优先级任务队列。
  3. 线程池任务异常处理:确保单个任务异常不会影响整个程序。

📓总结

掌握如何编写一个高效、安全的线程池对一个后端程序员来讲就像是西方不能没有耶路撒冷,毕竟如果要开发一个高并发需求的程序,例如抢票网站,服务器速度更不上、或者出现了线程安全,到时候用户就要寄律师函了。

📜博客主页:主页

📫我的专栏:C++

📱我的github:github

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/609719.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

第19讲:Ceph集群CrushMap规则定制与调优:从基础到高级应用

文章目录 1.CrushMap规则拓扑结构1.1.集群默认的CrushMap规则拓补图1.2.自定义的CrushMap规则拓补图 2.定制CrushMap规则的方法以及注意事项3.通过二进制文件编写一套CrushMap规则3.1.将系统默认的CrushMap规则导出3.2.根据需求编写CrushMap规则3.3.将编写好的规则导入到集群中…

Fastapi+docker+tortoise-orm+celery

因为项目是后期引入celery,所以导致构建docker的时候只有fastapi的项目&#xff0c;celery的重启比较麻烦 1.docker安装celery pip install celery安装celery的时候注意python版本与celery版本的适配&#xff0c;有些celery的版本不支持python的版本&#xff0c;具体的版本请看…

计算机毕业设计 | vue+springboot汽车销售管理系统(附源码)

1&#xff0c;项目介绍 本项目基于spring boot以及Vue开发&#xff0c;前端实现基于PanJiaChen所提供的开源后台项目vue-element-admin改造。 针对汽车销售提供客户信息、车辆信息、订单信息、销售人员管理、财务报表等功能&#xff0c;提供经理和销售两种角色进行管理。 2&…

记录如何查询域名txt解析是否生效

要查询域名的TXT记录&#xff0c;可以使用nslookup命令。具体步骤如下&#xff1a;12 打开命令行终端。输入命令 nslookup -qttxt 域名&#xff0c;将"域名"替换为你要查询的实际域名。执行命令后&#xff0c;nslookup会返回域名的TXT记录值。 如何查询域名txt解析是…

面试题库-项目

1.项目主要实现了哪些功能&#xff1f; 本项目是专门为校园食堂窗口定制的一款软件产品&#xff0c;包括系统管理后台和客户端两部分。其中系统管理后台主要提供给食堂内部员工使用&#xff0c;可以对餐厅的菜品、套餐、订单、员工等进行管理维护。客户端主要提供给学生及校职…

什么是分库分表?代表性框架有哪些?

在互联网系统开发过程中&#xff0c;所谓的分库分表并不是一个新概念。或者说&#xff0c;对于很多开发人员而言&#xff0c;说起分库分表&#xff0c;大家都或多或少有所了解&#xff0c;也都知道数据量大了就需要进行分库分表。但是究竟如何实现分库分表呢&#xff1f; 要想…

创建Spring Boot项目及配置

目录 一、创建项目所需要的插件 1、安装插件 二、创建项目 三、创建项目所面临的常见问题。 1、IDEA不能识别 2、无效的发行版本 3、确认jar包是否下载成功 一、创建项目所需要的插件 1、安装插件 首先需要在IDEA插件里面搜索Spring&#xff0c;选择Spring Boot Helper…

什么是短信群发上行和下行

短信群发是一种广泛应用于商业和个人通信的技术&#xff0c;通过一次多条的方式&#xff0c;可以快速高效地传递信息。在实际的群发过程中&#xff0c;会涉及到上行和下行的概念。本文将详细介绍什么是短信群发上行和下行&#xff0c;并解释它们的应用。 什么是短信群发上行 群…

Dbeaver连接一段时间不操作后断开的问题

右键数据库连接点击编辑连接点击初始化将连接保持改成60s

BW4HANA混合建模 用ADSO的哪个视图?

写日志的ADSO除了1,2,3表之外。还会有6,7,8view。8view是上了BW4HANA2.0之后激活ADSO就会生成的。如果旧版本没有8&#xff0c;那就RSDG_ADSO_ACTIVATE激活一下。 如果勾了外部HANA视图&#xff0c;那就等于说还有一个HANA view。 首先咱知道ADSO是BW里面用来物理存储&#xf…

做一个属于自己的软件-pyside6快速上手教程

首先环境需要安装python3和pip&#xff0c;软件使用pycharm&#xff0c;安装也都很简单 首先需要安装pyside6,在终端执行&#xff1a; pip install pyside6 然后进入可视化编辑界面 pyside6-designer 进入后创建即可 可以从左侧点击鼠标拉组件进入到中间的工作区&#xff…

BLIP和BLIP2 论文讲解

文章目录 BLIPIntroductionMethod模型架构预训练目标字幕和过滤&#xff08;Capfilt&#xff09; BLIP2IntroductionMethod模型结构Q-Former预训练第一阶段Q-Former预训练第二阶段 BLIP 论文&#xff1a; 《BLIP: Bootstrapping Language-Image Pre-training for Unified Visio…

详解BOM编程

华子目录 BOM编程window对象常见的window对象的属性常见的window对象的方法注意 history对象history对象的属性history对象的方法 screen 对象navigator 对象属性方法 location对象属性方法示例 BOM编程 JavaScript本质是在浏览器中运行&#xff0c;所以JavaScript提供了BOM&a…

一文详解FDA邮件认证证书的重要性及其应用

随着全球化和电子商务的飞速发展&#xff0c;跨国贸易和沟通变得越来越频繁。在这个过程中&#xff0c;邮件作为重要的沟通工具&#xff0c;其安全性和可信度成为了各方关注的焦点。FDA&#xff08;美国食品药品监督管理局&#xff09;邮件认证证书就是在这一背景下应运而生的一…

1W 3KVDC 隔离 稳压单输出 DC/DC 电源模块——TPV-SAR 系列

TPV-SAR系列产品是专门针对PCB上分布式电源系统中需要与输入电源隔离且输出精度要求较高的电源应用场合而设计。该产品适用于&#xff1b;1&#xff09;输入电源的电压变化≤5%&#xff1b;2&#xff09;输入输出之前要求隔离电压≥3000VDC&#xff1b;3&#xff09;对输出电压…

mac电脑如何安装java

1、检查当前系统的 Java 版本 打开终端,输入以下命令查看当前 Java 版本 /usr/bin/java -version 2、前往 Java 官网下载 Java JDK 打开 Java 官网 (https://www.java.com/zh-CN/download/) 并下载最新版本的 Java JDK。 3、安装 Java JDK 双击下载的 .dmg 文件启动安装程序…

【全开源】Java共享台信息共享系统源码

特色功能 信息整合与共享&#xff1a;该平台提供一站式信息整合服务&#xff0c;将各种类型的信息资源进行汇聚&#xff0c;方便用户快速查找和获取所需资源。多种共享功能&#xff1a;支持信息共享、共享车位、共享会议室、共享电动车等多种共享功能&#xff0c;提高资源利用…

Windows系统本地部署DrawDB数据库设计工具并实现无公网IP远程访问

文章目录 1. Windows本地部署DrawDB2. 安装Cpolar内网穿透3. 实现公网访问DrawDB4. 固定DrawDB公网地址 开发中很多时候都会使用到数据库&#xff0c;所以选择一个好用的数据库设计工具会让工作效率翻倍。在当今数字化时代&#xff0c;数据库管理是许多企业和个人项目的核心。设…

vue-fontawesome-elementui-icon-picker选择icon框架

第一步&#xff1a;安装vue-fontawesome-elementui-icon-picker依赖 npm install vue-fontawesome-elementui-icon-picker --save-dev 第二步&#xff1a;main.js配置 (放在element ui引入之后) import iconPicker from vue-fontawesome-elementui-icon-picker; Vue.use(ico…

Python-VBA函数之旅-setattr函数

目录 一、setattr函数的常见应用场景 二、setattr函数使用注意事项 三、如何用好setattr函数&#xff1f; 1、setattr函数&#xff1a; 1-1、Python&#xff1a; 1-2、VBA&#xff1a; 2、推荐阅读&#xff1a; 个人主页&#xff1a; https://blog.csdn.net/ygb_1024?…