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

news2024/11/17 12:34:21
在这里插入图片描述

💻文章目录

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


📄前言

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

线程池的原理

概念

当我们在一台多核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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

静态分析-RIPS-源码解析记录-02

这部分主要分析scanner.php的逻辑&#xff0c;在token流重构完成后&#xff0c;此时ini_get是否包含auto_prepend_file或者auto_append_file 取出的文件路径将和tokens数组结合&#xff0c;每一个文件都为一个包含require文件名的token数组 接着回到main.php中&#xff0c;此时…

一款功能强大的网络安全综合工具-PotatoTool

一、 简介 这款工具是一款功能强大的网络安全综合工具&#xff0c;旨在为安全从业者、红蓝对抗人员和网络安全爱好者提供全面的网络安全解决方案。它集成了多种实用功能&#xff0c;包括解密、分析、扫描、溯源等&#xff0c;为用户提供了便捷的操作界面和丰富的功能选择。 二…

《Fundamentals of Power Electronics》——状态空间平均法

文献中出现了许多交流变换器建模技术&#xff0c;包括电流注入法、电路平均法和状态空间平均法。尽管给定方法的支持者可能更喜欢用特定形式表示最终结果&#xff0c;但几乎所有方法的最终结果都是等效的。所有人都会赞同&#xff0c;平均和小信号线性化是PWM变换器建模的关键步…

厚德提问大佬答4:AI绘画生成的心得

遇到难题不要怕&#xff01;厚德提问大佬答&#xff01; 厚德提问大佬答 你是否对AI绘画感兴趣却无从下手&#xff1f;是否有很多疑问却苦于没有大佬解答带你飞&#xff1f;从此刻开始这些问题都将迎刃而解&#xff01;你感兴趣的话题&#xff0c;厚德云替你问&#xff0c;你解…

Element-plus修改input的placeholder文字颜色

需求 代码 .el-input__inner::placeholder {color: #666f8d !important; }

图像处理--空域滤波增强(原理)

一、均值滤波 线性滤波算法&#xff0c;采用的主要是邻域平均法。基本思想是使用几个像素灰度的某种平均值来代替一个原来像素的灰度值。可以新建一个MN的窗口以为中心&#xff0c;这个窗口S就是的邻域。假设新的新的像素灰度值为&#xff0c;则计算公式为 1.1 简单平均法 就是…

堆的应用2——TOPK问题

TOPK问题 TOP-K问题&#xff1a;即求数据结合中前K个最大的元素或者最小的元素&#xff0c;一般情况下数据量都比较大。 比如&#xff1a;专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。 情况1——数据量小 对于Top-K问题&#xff0c;能想到的最简单直接的方式就…

品鉴中的文化碰撞:如何理解和欣赏不同文化背景下的红酒

红酒作为世界各地广泛生产的产品&#xff0c;具有丰富的文化内涵。不同国家、地区和民族的红酒文化各具特色&#xff0c;反映了当地的历史、传统、习俗和生活方式。在品鉴云仓酒庄雷盛红酒时&#xff0c;理解和欣赏不同文化背景下的红酒是提升品鉴体验的重要一环。 首先&#x…

【Java orm 框架比较】九 新增wood框架对比

【Java orm 框架比较】九 新增wood框架对比 本次新增wood 框架测试 测试数据存储、分页查询&#xff0c;文档及框架比较稳定半天时间加入测试使用 迁移到&#xff08;https://gitee.com/wujiawei1207537021/spring-orm-integration-compare&#xff09; orm框架使用性能比较…

【STM32 |GPIO】GPIO结构、GPIO输出

目录 GPIO简介 GPIO的基本结构 GPIO位结构&#xff08;每一位的具体电路结构&#xff09; 输入 上拉和下拉电阻 斯密特触发器 ​编辑 输出 GPIO模式 ​编辑 浮空输入、上拉输入、下拉输入 模拟输入 开漏输出和推挽输出 复用开漏输出和复用推挽输出 LED和蜂鸣器…

代码随想录算法训练营第36期DAY22

DAY22 654最大二叉树 自己做的时候忽略了&#xff1a;nums.length>1的题给条件。所以每次递归都要判断是否size()>1&#xff0c;不要空的。 /** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *rig…

JSpdf,前端下载大量表格数据pdf文件,不创建dom

数据量太大使用dom》canvas》image》pdf.addimage方法弊端是canvas超出 浏览器承受像素会图片损害&#xff0c;只能将其切割转成小块的canvas,每一次调用html2canvas等待时间都很长累积时间更长&#xff0c;虽然最终可以做到抽取最小dom节点转canvas拼接数据&#xff0c;但是死…

[附源码+视频教程]暗黑纪元H5手游_架设搭建_畅玩三网全通西方3D世界_带GM

本教程仅限学习使用&#xff0c;禁止商用&#xff0c;一切后果与本人无关&#xff0c;此声明具有法律效应&#xff01;&#xff01;&#xff01;&#xff01; 教程是本人亲自搭建成功的&#xff0c;绝对是完整可运行的&#xff0c;踩过的坑都给你们填上了 一. 演示视频 暗黑纪…

Android 查看CUP占用率

查看每个进程CUP占用率的几种方式,由于自己充电界面老是导致整机温度过高&#xff0c;后面发现自己的线程一直在跑&#xff0c;相当于死循环&#xff0c;后面加上sleep才得以改善&#xff1b;先看看几种查询方式吧。 1、adb shell top 2、adb shell busybox top 3、adb shell …

C++类和对象中篇

&#x1f407; &#x1f525;博客主页&#xff1a; 云曦 &#x1f4cb;系列专栏&#xff1a;[C] &#x1f4a8;路漫漫其修远兮 吾将而求索 &#x1f49b; 感谢大家&#x1f44d;点赞 &#x1f60b;关注&#x1f4dd;评论 文章目录 &#x1f4d4;前言&#x1f4d4;1、类的六个…

微服务学习笔记

微服务学习笔记 文章目录 微服务学习笔记认识微服务微服务技术栈微服务学习要点微服务远程调用1)注册RestTemplate2) 服务远程调用RestTemplate Eureka注册中心简介操作过程搭建EurekaServer注册user-service在order-service完成服务拉取 Ribbon负载均衡IRule负载均衡策略饥饿加…

在线扭蛋机小程序:商家稳占市场的新突破口

近几年&#xff0c;扭蛋机进入了爆发期&#xff0c;动漫、游戏的发展更是推动了市场的发展&#xff0c;我国扭蛋机正在蓬勃发展中。 不过&#xff0c;在市场规模扩大下&#xff0c;扭蛋机行业的竞争力也在同时加大&#xff0c;企业商家需要在市场竞争中寻求发展新思路&#xf…

SEQUENTIAL CONSISTENCY----SC

SC模型是最直观的memory consistency model; 什么是single core sequential? 真正的执行顺序&#xff0c;和PO的顺序&#xff0c;是相同的&#xff1b; 什么是multi core sequential consistent? the operations of each individual processor (core) appear in this seq…

【知识碎片】2024_05_08

记录了两个代码和一个C语言switch的题。代码【错误的集合】使用到了hash&#xff0c;【密码检查】是对多个字符串输入的检查&#xff0c;有一些条件细节。两个代码都是关于数组内容操作的题目。 每日代码 1.错误的集合 错误的集合-力扣&#xff08;LeetCode 也是数组的操作&…

EOS智慧营销系统:突破传统,引领智慧营销新潮流

在数字化时代&#xff0c;智慧营销被誉为企业提升竞争力的关键。而EOS智慧营销系统作为一款领先于时代潮流的创新软件&#xff0c;正以其卓越的功能和不可替代的优势&#xff0c;引领着营销智能化的新时代。 EOS智慧营销系统的独特之处在于其全面而细致的市场分析能力。通过大数…