手撕线程池

线程池的目的:

1.复用线程,减少频繁创建和销毁的开销

  • 创建和销毁线程是昂贵的系统操作,涉及内核调度、内存分配;

  • 使用线程池预先创建一批线程,在多个任务间循环复用,避免资源浪费,提高性能

2.主线程不阻塞,让异步线程处理任务

  • 主线程可以专注于接收任务或调度逻辑;

  • 实际的耗时操作(如 IO、计算)由线程池中的工作线程执行;

  • 这样可以提升程序响应速度、增强并发能力

3.控制线程数量,避免线程过多导致资源竞争

线程池原理:

任务队列+一组工作线程+条件变量调度机制(唤醒阻塞的工作线程处理任务)

1.任务队列 1.提供push放入任务的接口 2.pop获取任务的接口(没有 工作线程会阻塞)         3.cancel析构线程池 通过mutex保证同步+condition_variable控制工作线程

2.调度器 1.初始化创建工作线程 2.提供工作线程的入口函数work()从队列中循环pop获取任务处理 没有任务会阻塞的pop函数中。3.提供Post 主线程向队列中放入任务的接口。

单生产者对多消费者(单队列)

1.调度器ThreadPool

1.构造函数

创建一定数量的工作线程 并进行管理。

2.Woker() 工作线程的入口函数

循环处理队列中的任务,通过Pop()获取任务 输出型参数task带回任务,没有任务就回阻塞在Pop()函数中。

3.Post() 向队列中放入任务的接口

bind绑定函数 将闭包对象存入任务队列中

4.析构函数

对每个退出的线程进行join()等待回收

2.任务队列

1.Push()入队列接口

先加锁 把任务放入到队列中 解锁后再用条件变量唤醒一个工作线程,进行处理。

2.Pop() 出队列

输出型参数value 返回给上层任务。条件变量阻塞线程,等有队列有任务才会解除阻塞 或者退出的时候。

3.Cancel() 

线程池析构逻辑,设置为非阻塞_nonblock=true,并唤醒所有线程 进行退出。

此时在Pop()被阻塞的线程被唤醒 返回false,工作线程break退出循环。

多生产者对多消费者(双队列)

上面单队列的适合一个生产者对应多个消费者的场景,如果多对多 那锁的冲突就大了。对此我们采用的是双缓冲区的策略,即生产者和消费者各一个队列。

工作线程消费完工作队列中的数据后 会进行交换队列的操作,继续进行处理。

1.调度器

不改变也没问题,但对于这个向队列中放入任务的Post()函数 原本只能存入std::function<void()>类型的函数。为了解决函数有返回值,上层获取到返回值,我们这样设置Post函数。

这是一个线程池任务提交函数(Post),它接受任意可调用对象 f 和参数 args...,并返回一个 std::future,用于异步获取结果。

1.实现要解决 传入的函数F的返回值类型是什么。可以用类型萃取工具std::invoke_result_t<>获取F函数 参数Args...的返回值类型。但参数的类型等模板推导完才行,因此采用了

auto post()->类型 尾置返回值类型的语法(告诉编译器等模板推导完再 告诉你返回值的类型)

2.现在知道了返回值类型,怎么把返回值给上层?

1.promise+futrue 在任务函数中手动设置结果。但还要更好的选择

2.packaged_task<>+future异步任务封装器,对函数进行封装 返回闭包对象。让线程执行这个闭包对象 等函数执行完自动把结果进行设置,就不用手动设置结果了。

1.template <typename F, typename... Args>        

   F代表函数类型  ...Args函数的参数     

2.std::invoke_result_t<F, Args...>类型萃取工具

    用来获取调用某个函数对象 F 以参数 Args... 所产生的返回类型。让编译器自己推导函数的返回类型 

3.auto Post(...) -> std::future<std::invoke_result_t<F, Args...>>尾置返回类型语法

 它是函数返回类型的一种写法,主要用于模板函数中返回类型依赖参数类型的情况

        1.auto让编译器先跳过函数的返回类型 先去处理函数的参数

        2.推导出函数参数F Args...的类型

        3.推导完函数参数的类型 才能推导出函数的返回值类型

为什么不能写成这样?

std::future<std::invoke_result_t<F, Args...>> Post(F&& f, Args&&... args);

因为编译器解析函数声明时是从左到右的,获取返回值的类型std::future<std::invoke_result_t<F, Args...>> 但F Args..的类型必须等参数传入后,才能确认。

编译器处理函数模板时是按“返回值 → 函数名 → 参数”的顺序进行的,你不能在返回值里使用还没推导出的模板参数类型,这就是为什么需要尾置返回类型的根本原因。

4.std::packaged_task<>异步任务封装器

std::packaged_task 是一个 把函数和返回值绑定起来的包装器可以让函数的执行结果通过 std::future 异步获取。

它是 C++ 标准库中为了解决“函数执行完以后怎么拿返回值”的问题而设计的组件。

eg.使用方法

int add(int a, int b) { return a + b; }
//1.用 packaged_task+bind 包装它函数
std::packaged_task<int()> task(std::bind(add, 2, 3));
//2.获取关联的future
std::future<int> result = task.get_future();
//3.异步执行这个任务(线程、线程池、手动调用都可以)
std::thread t(std::move(task));  // 线程里执行 task()
t.join();
//4.fet()获取返回值
std::cout << result.get();

和promise最大的区别是,不需要手动设置set_value的值,等bind绑定的函数执行完 自动把函数的返回值填入,之后get()就可以获取到函数的返回值。

对比点std::packaged_taskstd::promise
是否自动设置 future 的值?✅ 是的,执行函数后自动设置❌ 需要你手动调用 .set_value()
谁负责填充结果?函数执行完后自动填充你手动调用 .set_value()
是否必须绑定函数?✅ 是的❌ 否,传的是值

packaged_task 最适合线程池,因为线程池就是“执行任务然后获得返回值”,它天然对接任务队列。

“执行一个函数并得到它的返回值” —— 用 packaged_task;

promise 更多用于线程间通信,或者异步控制信号(比如网络回调、I/O完成通知等)。

“告诉另一个线程一个值” —— 用 promise。

2.任务队列(双)

和单队列不同的地方是,Pop()工作线程获取任务时 如果没有任务,不会阻塞在Pop()而是会进行Swap()交换队列(生产者队列有数据或者非阻塞)

        if(_con_queue.empty()&&SwapQueue()==0)

先判断消费者队列是否为空,不为空就不会继续后面的判断了,也就不会进行交互队列。

只有消费者队列为空才会进行交换。SwapQueue()会返回交换后的队列有多少任务 ==0说明队列为空(但生产者队列有数据才会交换 没有数据就会阻塞,除非要退出 会设置为非阻塞)

,所以交换完队列还为空就需要返回false进行退出。

交换时 处理消费者需要加锁,生产者也需要加锁。

等生产者队列有数据再进行交换,或者退出设为非阻塞

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

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

相关文章

3DTiles三维模型

1. 3DTiles 介绍​ 2016 年&#xff0c;Cesium 团队借鉴传统 2DGIS 的地图规范&#xff1a;WMTS&#xff0c;借鉴图形学中的层次细节模型&#xff0c;打造出大规模的三维数据标准&#xff1a;3d-Tiles&#xff0c;中文译名&#xff1a;三维瓦片。 它在模型上利用了 gltf 渲染…

Golang Kratos 系列:业务分层的若干思考(一)

在使用 Kratos 框架开发云服务的过程中&#xff0c;渐渐理解和感受到“领域层”这个概念和抽象的强大之处&#xff0c;它可以将业务和存储细节解耦、将业务和开发初期频繁变更的API结构&#xff0c;让Mock单元测试变得更加容易、对细节的变化更鲁棒。让业务代码摆脱技术细节依赖…

深度优化OSS上传性能:多线程分片上传 vs 断点续传实战对比

1 卸载开头 对象存储服务&#xff08;OSS&#xff09;已成为现代应用架构的核心组件&#xff0c;但随着业务规模扩大&#xff0c;文件上传性能问题日益凸显。本文将深入探讨两种核心优化技术&#xff1a;多线程分片上传和断点续传&#xff0c;通过理论分析、代码实现和性能测试…

doris_工作使用整理

文章目录 前言一、doris整体情况二、doris的存储过程情况1.分类2. 同步物化视图3. 异步物化视图三,分区相关1.分区建的过多前言 提示:doris使用版本3.x 提示:以下是本篇文章正文内容,下面案例可供参考 一、doris整体情况 细节放大 二、doris的存储过程情况 1.分类 按…

左神算法之单辅助栈排序算法

目录 1. 题目2. 解释3. 思路4. 代码5. 总结 1. 题目 请编写一个程序&#xff0c;对一个栈里的整型数据&#xff0c;按升序进行排序&#xff08;即排序前栈里的数据是无序的&#xff0c;排序后最大元素位于栈顶&#xff09;。要求最多只能使用一个额外的栈存放临时数据&#xf…

使用Trae编辑器与MCP协议构建高德地图定制化服务

目录 一、使用Trae编辑器配置高德MCP Server 1.1 Trae介绍 1.2 从mcp.so中获取配置高德地图mcp server配置信息 1.3 高德地图开发者配置 1.4 添加Filesystem 到Trae 1.5 使用结果展示 1.6 MCP常见命令行工具和包管理说明 1.7 Function Call工具和MCP技术对比 二、本地…

【LLaMA-Factory 实战系列】三、命令行篇 - YAML 配置与高效微调 Qwen2.5-VL

【LLaMA-Factory 实战系列】三、命令行篇 - YAML 配置与高效微调 Qwen2.5-VL 1. 引言2. 为什么从 WebUI 转向命令行&#xff1f;3. 准备工作&#xff08;回顾&#xff09;4. 核心&#xff1a;创建并理解训练配置文件4.1 选择并复制基础模板4.2 逐一解析与修改配置文件4.3 参数详…

推荐:ToB销售B2B销售大客户营销大客户销售培训师培训讲师唐兴通讲销售技巧数字化销售销AI销售如何有效获取客户与业绩

站在AI浪潮之巅&#xff0c;重塑销售之魂 在AI时代&#xff0c;普通销售人员&#xff08;TOB、TOC&#xff09;除了传统的销售动作之外&#xff0c;还能做什么&#xff1f;怎么做&#xff1f; 这是《AI销冠》这本书想探讨的核心问题。 特别喜欢编辑老师总结的&#xff1a; 读者…

爬取小红书相关数据导入到excel

本期我们来进行实战,爬取小红书的相关数据导入到excel中,后续可进行些数据分析,今后或者已经在运营小红书的小伙伴应该比较喜欢这些数据。今天我们的主角是DrissionPage,相对于之前介绍的selenium省去了很多的配置,直接安装了就能使用。 DrissionPage 是一个基于 python …

c++面试题每日一学记录- C++对象模型与内存对齐深度原理详解

一、C++对象模型核心原理 1. 对象内存布局基础原理 设计哲学: 零开销原则:不为未使用的特性付出代价(如无虚函数则无vptr)兼容性:C结构体在C++中保持相同内存布局多态支持:通过虚函数表实现运行时动态绑定内存布局实现机制: 编译器处理步骤: 成员排列:严格按声明顺序…

Kafka 监控与调优实战指南(二)

五、Kafka 性能问题剖析 5.1 消息丢失 消息丢失是 Kafka 使用过程中较为严重的问题&#xff0c;可能由多种原因导致。在生产者端&#xff0c;如果配置不当&#xff0c;比如将acks参数设置为0&#xff0c;生产者发送消息后不会等待 Kafka broker 的确认&#xff0c;就继续发送…

Linux下SVN报错:Unable to connect to a repository at URL ‘svn://XXX‘

一、问题描述 Linux下通过SVN执行提交&#xff08;commit&#xff09;操作时报错&#xff1a;Unable to connect to a repository at URL svn://XXX&#xff1a; 二、解决方法 导致该问题的一个可能原因是远程仓库的URL发生变化了&#xff0c;即svn服务器的ip变更了。这时可…

Modbus 扫描 从站号、波特率

下载链接&#xff1a;https://pan.quark.cn/s/533ceb8e397d 下载链接: https://pan.baidu.com/s/1PQHn-MwfzrWgF2UrXQDoGg 提取码: 1111

Docker 容器通信与数据持久化

目录 简介 一、Docker 容器通信 1. Docker 网络模式 2. Bridge 模式 3. Host 模式 4. Container 模式 5. Overlay 模式 6. 端口映射&#xff1a;容器与外部的桥梁 7. 容器互联&#xff1a;从 --link 到自定义网络 二、Docker 数据持久化 1. 数据卷&#xff1a;Docke…

【教学类-89-08】20250624新年篇05——元宵节灯笼2CM黏贴边(倒置和正立数字 )

背景需求&#xff1a; 【教学类-89-06】20250220新年篇05——元宵节灯笼2CM黏贴边&#xff08;3边形到50边形&#xff0c;一页1图、2图、4图&#xff0c;适合不同水平&#xff0c;适合不同阶段&#xff09;-CSDN博客文章浏览阅读1.6k次&#xff0c;点赞35次&#xff0c;收藏27…

【DB2】SQL0104N An unexpected token “OCTETS“ was found following “……

db2创建表时报标题的错误&#xff0c;建表语句如下 db2 "CREATE TABLE YS.TEST_1(ID VARCHAR(64 OCTETS))"去掉octets就好了 经过测试&#xff0c;在9.7版本报错&#xff0c;在10.5.11没问题&#xff0c;怀疑版本差异导致 在官网查找资料&#xff0c;应该是10.5才…

暴雨以信创委员会成员单位身份参与南京专题活动

6月19日&#xff0c;中国电子工业标准化技术协会信息技术应用创新工作委员会&#xff08;简称信创工委会&#xff09;联合南京市工业和信息化局共同举办的“智启未来&#xff1a;AI赋能信息技术应用创新办公新势力”专题活动在南京成功举办。南京市工业和信息化局副局长代吉上、…

基于keepalived、vip实现高可用nginx (centos)

基于keepalived、vip实现高可用nginx &#xff08;centos&#xff09; 1、安装keepalived yum install keepalived2、选同一局域网空置ip作vip 我这里测试是&#xff1a; 主&#xff1a;192.168.163.134 副&#xff1a;192.168.163.135 vip&#xff1a;192.168.163.1403、ke…

使用 launch 启动 rviz2 并加载机器人模型

视频资料&#xff1a;《ROS 2机器人开发从入门到实践》6.2.2 在RViz中显示机器人_哔哩哔哩_bilibili 1、创建工作空间 chapt6_ws/src&#xff0c;创建包 fishrobot_description ros2 create fishrobot_description --build-type ament_cmake --license Apache-2.0 2、创建机器…

华为云Flexus+DeepSeek征文 | 基于CCE容器的AI Agent高可用部署架构与弹性扩容实践

华为云FlexusDeepSeek征文 | 基于CCE容器的AI Agent高可用部署架构与弹性扩容实践 &#x1f31f; 嗨&#xff0c;我是IRpickstars&#xff01; &#x1f30c; 总有一行代码&#xff0c;能点亮万千星辰。 &#x1f50d; 在技术的宇宙中&#xff0c;我愿做永不停歇的探索者。 …