C++实现一个线程池

2023-11-12

一、为什么使用线程池

大家都知道C++支持多线程开发,也就是支持多个任务并行运行,我们也知道线程的生命周期中包括创建、就绪、运行、阻塞、销毁等阶段,所以如果要执行的任务很多,每个任务都需要一个线程的话,那么频繁的创建、销毁线程会比较耗性能。

有了线程池就不用创建更多的线程来完成任务,它可以:

降低资源的消耗,通过重复利用已经创建的线程降低线程创建和销毁造成的消耗。

提高相应速度,当任务到达的时候,任务可以不需要等到线程创建就能立刻执行。

提高线程的可管理性,线程是稀缺资源,使用线程池可以统一的分配、调优和监控。

二、线程池的原理

通俗的讲,线程池就是一个线程集合,里面已经提前创建好了若干个线程,当需要线程的时候到线程集合里获取一个即可,这样省去了创建线程的时间,当然也省去了系统回收线程的时间,当线程池里的线程都被使用了后,只能阻塞等待了,等待获取线程池后被释放的线程。

线程池主要包括任务队列,线程队列,线程池类,线程池管理类和任务类等组成部分。当线程池提交一个任务到线程池后,执行流程如下:

线程池先判断核心线程池里面的线程是否都在执行任务。如果不是都在执行任务,则创建一个新的工作线程来执行任务。如果核心线程池中的线程都在执行任务,则判断工作队列是否已满。如果工作队列没有满,则将新提交的任务存储到这个工作队列中,如果工作队列满了,线程池则判断线程池的线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理 ,也就是拒接策略。
一句话:管理一个任务队列,一个线程队列,然后每次去一个任务分配给一个线程去做,循环往复。

三、代码实现

有什么问题?线程池一般是要复用线程,所以如果是取一个task分配给某一个thread,执行完之后再重新分配,在语言层面这是基本不能实现的:C++的thread都是执行一个固定的task函数,执行完之后线程也就结束了。所以该如何实现task和thread的分配呢?

让每一个thread创建后,就去执行调度函数:循环获取task,然后执行。

这个循环该什么时候停止呢?

很简单,当线程池停止使用时,循环停止。

这样一来,就保证了thread函数的唯一性,而且复用线程执行task。

总结一下,我们的线程池的主要组成部分有二:

  • 任务队列(Task Queue)
  • 线程池(Thread Pool)

#ifndef THREAD_POOL_H
#define THREAD_POOL_H
 
#include <vector>
#include <queue>
#include <memory>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <future>
#include <functional>
#include <stdexcept>
 
class ThreadPool {
 
public:
    ThreadPool(size_t);                          //构造函数
    template<class F, class... Args>             //类模板
    auto enqueue(F&& f, Args&&... args)->std::future<decltype(func(args...))>;//任务入队
    ~ThreadPool();                              //析构函数
 
private:
    std::vector< std::thread > workers;            //线程队列,每个元素为一个Thread对象
    std::queue< std::function<void()> > tasks;     //任务队列,每个元素为一个函数对象    
 
    std::mutex queue_mutex;                        //互斥量
    std::condition_variable condition;             //条件变量
    bool stop;                                     //停止
};
 
// 构造函数,把线程插入线程队列,插入时调用embrace_back(),用匿名函数lambda初始化Thread对象
inline ThreadPool::ThreadPool(size_t threads) : stop(false){
 
    for(size_t i = 0; i<threads; ++i)
        workers.emplace_back(
            [this]
            {
                for(;;)
                {
                    // task是一个函数类型,从任务队列接收任务
                    std::function<void()> task;  
                    {
                        //给互斥量加锁,锁对象生命周期结束后自动解锁
                        std::unique_lock<std::mutex> lock(this->queue_mutex);
                        
                        //(1)当匿名函数返回false时才阻塞线程,阻塞时自动释放锁。
                        //(2)当匿名函数返回true且受到通知时解阻塞,然后加锁。
                        this->condition.wait(lock,[this]{ return this->stop || !this->tasks.empty(); });
                       
                         if(this->stop && this->tasks.empty())
                            return;
                        
                        //从任务队列取出一个任务
                        task = std::move(this->tasks.front());
                        this->tasks.pop();
                    }                            // 自动解锁
                    task();                      // 执行这个任务
                }
            }
        );
}
 
// 添加新的任务到任务队列
template<class F, class... Args>
auto ThreadPool::enqueue(F&& f, Args&&... args)->std::future<decltype(func(args...))>
{
    // 获取函数返回值类型        
    using return_type = decltype(func(args...));
 
    // 创建一个指向任务的智能指针
    auto task = std::make_shared< std::packaged_task<return_type()> >(
            std::bind(std::forward<F>(f), std::forward<Args>(args)...)
        );
        
    std::future<return_type> res = task->get_future();
    {
        std::unique_lock<std::mutex> lock(queue_mutex);  //加锁
        if(stop)
            throw std::runtime_error("enqueue on stopped ThreadPool");
 
        tasks.emplace([task](){ (*task)(); });          //把任务加入队列
    }                                                   //自动解锁
    condition.notify_one();                             //通知条件变量,唤醒一个线程
    return res;
}
 
// 析构函数,删除所有线程
inline ThreadPool::~ThreadPool()
{
    {
        std::unique_lock<std::mutex> lock(queue_mutex);
        stop = true;
    }
    condition.notify_all();
    for(std::thread &worker: workers)
        worker.join();
}
 
#endif

使用

#include <iostream>
#include <chrono>
#include "ThreadPool.h"
 
void func()
{
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    std::cout<<"worker thread ID:"<<std::this_thread::get_id()<<std::endl;
}
 
int main()
{
    ThreadPool pool(4);
    while(1)
    {
       pool.enqueue(func);
    }
}

打印

另一种实现,

其中线程池类 ThreadPool 维护了一个任务队列 tasks_,并且可以动态添加任务到队列中。线程池管理类 ThreadPoolManager 提供了获取线程池实例的接口,使用时只需要通过 ThreadPoolManager::getThreadPool() 获取线程池实例,然后调用 enqueue 方法添加任务即可 

#include <iostream>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <functional>

using namespace std;

// 任务类
class Task {
public:
    Task(function<void()> f) : func(f) {}
    void operator()() { func(); }
private:
    function<void()> func;
};

// 线程池类
class ThreadPool {
public:
    ThreadPool(int size) : stop(false) {
        for (int i = 0; i < size; ++i) {
            threads.emplace_back([this] {
                while (true) {
                    function<void()> task;
                    {
                        unique_lock<mutex> lock(mutex_);
                        condition_.wait(lock, [this] { return stop || !tasks_.empty(); });
                        if (stop && tasks_.empty()) return;
                        task = move(tasks_.front());
                        tasks_.pop();
                    }
                    task();
                }
            });
        }
    }

    ~ThreadPool() {
        {
            unique_lock<mutex> lock(mutex_);
            stop = true;
        }
        condition_.notify_all();
        for (auto& thread : threads) {
            thread.join();
        }
    }

    template<typename F>
    void enqueue(F&& f) {
        {
            unique_lock<mutex> lock(mutex_);
            tasks_.emplace(forward<F>(f));
        }
        condition_.notify_one();
    }

private:
    vector<thread> threads;
    queue<function<void()>> tasks_;
    mutex mutex_;
    condition_variable condition_;
    bool stop;
};

// 线程池管理类
class ThreadPoolManager {
public:
    static ThreadPool& getThreadPool() {
        static ThreadPool threadPool(thread::hardware_concurrency());
        return threadPool;
    }
};

int main() {
    ThreadPool& threadPool = ThreadPoolManager::getThreadPool();
    for (int i = 0; i < 10; ++i) {
        threadPool.enqueue(Task([] { cout << "Hello, World!" << endl; }));
    }
    return 0;
}

参考:

基于C++11实现线程池 - 知乎

C++实现线程池_蓬莱道人的博客-CSDN博客_c++ 线程池

C++实现线程池_晓枫寒叶的博客-CSDN博客_c++ 线程池
C++17future类+可变参模板实现线程池_刚入门的代码spa技师的博客-CSDN博客_c++17 可变参

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++实现一个线程池 的相关文章

  • 如何在 VS 中键入时显示方法的完整文档?

    标题非常具有描述性 是否有任何扩展可以让我看到我正在输入的方法的完整文档 我想查看文档 因为我可以在对象浏览器中看到它 其中包含参数的描述和所有内容 而不仅仅是一些 摘要 当然可以选择查看所有覆盖 它可能是智能感知的一部分 或者我不知道它并
  • VS30063:您无权访问 https://dev.azure.com

    我正在尝试在 asp net core 2 1 mvc 应用程序中使用以下代码连接 Azure DevOps Uri orgUrl new Uri https dev azure com xxxxx String personalAcces
  • C++11 函数局部静态 const 对象的线程安全初始化

    这个问题已在 C 98 上下文中提出 并在该上下文中得到回答 但没有明确说明有关 C 11 的内容 const some type create const thingy lock my lock some mutex static con
  • 为什么 std::allocator 在 C++17 中丢失成员类型/函数?

    一边看着std 分配器 http en cppreference com w cpp memory allocator 我看到成员 value type pointer const pointer reference const refer
  • 禁用 LINQ 上下文的所有延迟加载或强制预先加载

    我有一个文档生成器 目前包含约 200 个项目的查询 但完成后可能会超过 500 个 我最近注意到一些映射表示延迟加载 这给文档生成器带来了一个问题 因为它需要根据生成的文档来访问所有这些属性 虽然我知道DataLoadOptions可以指
  • 事件日志写入错误

    很简单 我想向事件日志写入一些内容 protected override void OnStop TODO Add code here to perform any tear down necessary to stop your serv
  • 如何在 Java 中创建接受多个值的单个注释

    我有一个名为 Retention RetentionPolicy SOURCE Target ElementType METHOD public interface JIRA The Key Bug number JIRA referenc
  • 是否可以使用 Java Guava 将函数应用于集合?

    我想使用 Guava 将函数应用于集合 地图等 基本上 我需要调整 a 的行和列的大小Table分别使所有行和列的大小相同 执行如下操作 Table
  • C# using 语句、SQL 和 SqlConnection

    使用 using 语句 C SQL 可以吗 private static void CreateCommand string queryString string connectionString using SqlConnection c
  • UWP 无法在两个应用程序之间创建本地主机连接

    我正在尝试在两个 UWP 应用程序之间设置 TCP 连接 当服务器和客户端在同一个应用程序中运行时 它可以正常工作 但是 当我将服务器部分移动到一个应用程序并将客户端部分移动到另一个应用程序时 ConnectAsync 会引发异常 服务器未
  • 过期时自动重新填充缓存

    我当前缓存方法调用的结果 缓存代码遵循标准模式 如果存在 则使用缓存中的项目 否则计算结果 在返回之前将其缓存以供将来调用 我想保护客户端代码免受缓存未命中的影响 例如 当项目过期时 我正在考虑生成一个线程来等待缓存对象的生命周期 然后运行
  • 32位PPC rlwinm指令

    我在理解上有点困难rlwinmPPC 汇编指令 旋转左字立即然后与掩码 我正在尝试反转函数的这一部分 rlwinm r3 r3 0 28 28 我已经知道什么了r3 is r3在本例中是一个 4 字节整数 但我不确定这条指令到底是什么rlw
  • 是否有一个 C++ 库可以从 PDF 文件中提取文本,例如 PDFBox for Java? [关闭]

    Closed 此问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 去年 我使用 PDFBox 在 Java 中创建了一个应用程序来获取某些 PDF 文件中的原始文本 现在
  • Android:无法发送http post

    我一直在绞尽脑汁试图弄清楚如何在 Android 中发送 post 方法 这就是我的代码的样子 public class HomeActivity extends Activity implements OnClickListener pr
  • Java中HashMap和ArrayList的区别?

    在爪哇 ArrayList and HashMap被用作集合 但我不明白我们应该在哪些情况下使用ArrayList以及使用时间HashMap 他们两者之间的主要区别是什么 您具体询问的是 ArrayList 和 HashMap 但我认为要完
  • 如何在 GCC 5 中处理双 ABI?

    我尝试了解如何克服 GCC 5 中引入的双重 ABI 的问题 但是 我没能做到 这是一个重现错误的非常简单的示例 我使用的GCC版本是5 2 如您所见 我的主要函数 在 main cpp 文件中 非常简单 main cpp include
  • org.apache.commons.net.io.CopyStreamException:复制时捕获 IOException

    我正在尝试使用以下方法中的代码将在我的服务器中创建的一些文件复制到 FTP 但奇怪的是我随机地低于错误 我无法弄清楚发生了什么 Exception org apache commons net io CopyStreamException
  • 洪水填充优化:尝试使用队列

    我正在尝试创建一种填充方法 该方法采用用户指定的初始坐标 检查字符 然后根据需要更改它 这样做之后 它会检查相邻的方块并重复该过程 经过一番研究 我遇到了洪水填充算法并尝试了该算法 它可以工作 但无法满足我对 250 x 250 个字符的数
  • 在java中使用多个bufferedImage

    我正在 java 小程序中制作游戏 并且正在尝试优化我的代码以减少闪烁 我已经实现了双缓冲 因此我尝试使用另一个 BufferedImage 来存储不改变的游戏背景元素的图片 这是我的代码的相关部分 public class QuizApp
  • 如何创建向后兼容 Windows 7 的缩放和尺寸更改每显示器 DPI 感知应用程序?

    我是 WPF 和 DPI 感知 API 的新手 正在编写一个在 Windows 7 8 1 和 10 中运行的应用程序 我使用具有不同每个显示器 DPI 设置的多个显示器 并且有兴趣将我的应用程序制作为跨桌面配置尽可能兼容 我已经知道可以将

随机推荐

  • c++中模板与重载

    include
  • 常见的Linux高危端口有哪些

    一些常见的 Linux 高危端口 SSH 端口 22 SSH 虽然是一种安全的远程登录协议 但默认端口号 22 却经常成为黑客攻击的目标 因此 建议将 SSH 服务的端口号修改为一个不易被猜测的端口号 以增强系统安全性 Telnet 端口
  • 使用Go Hijack和jQuery轻松实现异步推送服务

    使用Go Hijack和jQuery轻松实现异步推送服务 首先要说明的是 这里实现的异步推送服务采用的是Long Polling方式 并不是Comet 如果想用Comet来实现的话 可以参考这个开源项目 http cometd org 不过
  • stm32通过esp8266实现温湿度实时监控和控制灯光

    stm32通过esp8266实现温湿度实时监控和控制灯光 前言 准备材料 一 硬件方面 二 软件方面 三 建立工程 1 工程创建 2 代码编写 三 关于一些C语言函数运用 结果演示 视频演示 前言 WiFi具有两种功能模式 一种叫 AP A
  • VMware Workstation 不可恢复错误: (vcpu-0) vcpu-0:VERIFY vmcore/vmm/main/cpuid.c:376 bugNr=1036521

    这个问题的原因有几个 第一个原因就是网上说的要开启BIOS的Interl virtual technology 而我当时开启了之后依然报错 最后找到问题的原因在VMWare Workstation 右键我们的虚拟机 选择最下面的设置 然后注
  • 梁山派GD32F470 CMSIS-DAPv1驱动错误

    请先参考嘉立创提供的解决方法 https dri8c0qdfb feishu cn wiki wikcnsGSBwwp15hr9dqRqbiKqxe 我的错误出现情况非常特别 仅供参考 本人电脑 2021拯救者r9000p win11 串口
  • 遗传算法--旅行商问题(TSP问题)-Matlab

    1 问题 2 仿真过程 3 代码实现 1 旅行商问题 TSP问题 假设有一个旅行商人要拜访全国31个省会城市 它需要选择所要走的路径 路径的限制是每个城市只能拜访一次 而且最后要回到原来出发的城市 对路径选择的要求是 所选路径的路成为所有路
  • QT笔记-PDF阅读器(附带完整源码)- 导入PDF文件,在窗体上显示,并提取PDF的文字内容

    环境搭建 1 mupdf 1 17 0 source库 mupdf 1 17 0 source默认是VS2019的工程 由于示例用的是VS2017 所以需要用VS2017重新编译mupdf 1 17 0 source工程 生成LIB库文件
  • k8s部署redis一主两从三哨兵

    目录 一 部署思路 二 部署 1 编写namespace脚本 2 编写configmap脚本 3 编写secret脚本 4 编写StorageClass脚本 1 编写ServiceAccount ClusterRole ClusterRol
  • 建站心得之discuz门户程序相比ZBLOG具有哪些优势[图]

    以前我是采用discuz门户程序建站的 因为个人觉得 这套程序确实不错 而门户discuz门户也可以生成纯静态HTML文件 这对于我们有特殊要求的站长来说 非常重要 因为纯静态不仅可以提升网页的访问速度 还可以节省服务器成本 因为我们都知道
  • Java异步执行方法

    一 利用多线程 直接new线程 Thread t new Thread Override public void run longTimeMethod 使用线程池 private ExecutorService executor Execu
  • 机器学习实验1---决策树预测泰坦尼克数据集

    泰坦尼克号乘客数据集分析 ID3算法决策树 泰坦尼克问题是一个比较经典的案例 此次实验的目的在于用决策树进行乘客的生存预测 数据集中的具体字段为 数据 含义 PassengerId 乘客编号 Survived 是否幸存 Pclass 船票等
  • 为什么要做一款ERP软件——开源软件诞生7

    技术之外的探讨 第7篇 用日志记录 开源软件 的诞生 赤龙ERP开源地址 点亮星标 感谢支持 与开发者交流 kzca2000 码云 https gitee com redragon redragon erp GitHub https git
  • 微搭低代码学习之销售员销售目标采集系统开发

    四月二十三 春意渐浓 草木抽出新枝 鸟儿唱响欢融 花开满园 香气袭人 阳光明媚 人们心情舒畅 愿这美好的日子 伴随你一生 使用Notion AI提问 写一首关于4月23日的诗 文章目录 前言 一 销售员销售目标采集系统需求分析 一 需求背景
  • Matplotlib绘制混淆矩阵及colorbar标签设置

    本文提供一种通过Matplotlib绘制混淆矩阵并调整colorbar标签的程序 直接上程序 from sklearn metrics import confusion matrix import matplotlib pyplot as
  • JS从编译到运行代码的过程

    js运行分为两个阶段 具体AST树以及bytecode等名词看我上一篇文章 浏览器工作原理 1 编译阶段 js代码 gt AST树 代码被解析的过程 v8引擎内部会在堆内存帮助我们创建一个对象 GlobalObject gt GO 简称GO
  • JS闭包理解

    JS闭包 1 闭包 每次看到jQuery的时候 首先想到的就是闭包 这是个常谈的问题了 今天重新回忆了一下闭包 什么是闭包 当有一个函数想要访问另一个函数内部的变量 这个是访问不了的 所有我们要用闭包来访问 所以简单的来说 闭包就是连接函数
  • STM32使用IIC协议驱动0.96寸OLED屏

    IIC是常用的协议之一 它通过不同的地址来区分设备 并且端口需要是开漏模式 并且需要接上拉电阻 要使用IIC驱动OLED 首先要配置IIC void I2C Configuration void I2C InitTypeDef I2C In
  • 程序员工资大概组成【刚毕业的大学生看过来】

    一 程序员的薪资组成是什么样子的呢 薪资组成因人而异 受到很多因素的影响 如工作地点 工作经验 工作职责 专业领域等 一般而言 中国程序员的薪资组成包括基本工资 绩效工资 津贴和奖金等 在中国 程序员的平均薪资水平受到地区 行业和职业经验等
  • C++实现一个线程池

    一 为什么使用线程池 大家都知道C 支持多线程开发 也就是支持多个任务并行运行 我们也知道线程的生命周期中包括创建 就绪 运行 阻塞 销毁等阶段 所以如果要执行的任务很多 每个任务都需要一个线程的话 那么频繁的创建 销毁线程会比较耗性能 有