C++八股 —— 手撕线程池
文章目录
- 一、背景
- 二、线程池实现
- 1. 任务队列和工作线程
- 2. 构造和析构函数
- 3. 添加任务函数
- 4. 完整代码
- 三、阻塞队列实现
- 1. 基础队列
- 2. 升级版队列
- 四、测试代码
- 五、相关问题
- 六、其他实现方式
来自:华为C++一面:手撕线程池_哔哩哔哩_bilibili
华为海思:
手撕线程池
相关概念参考:
- C++八股——函数对象、Lambda、bind、function_c++八股文-CSDN博客
- C++ 11 lock_guard 和 unique_lock_lockguard-CSDN博客
- explicit关键字
- C++ —— 可变参数_c++ 可变参数-CSDN博客
- 阻塞队列(超详细易懂)-CSDN博客
一、背景
-
什么是线程池
维持管理一定数量线程的池式结构。
核心思想:线程复用。 避免频繁地创建和销毁线程带来的开销。
-
为什么需要线程池
- 创建/销毁线程的开销大,线程池可以有效降低资源消耗、提高响应速度
- 提高线程的可管理性
- 防止因任务过多导致无限制创建线程而耗尽系统资源的问题。
-
线程池的工作流程
核心为生产者-消费者模型
线程池需要维护工作线程(消费者线程)和一个任务队列,生产者线程创建任务放入线程池的任务队列,消费者线程从任务队列中取出任务执行。
二、线程池实现
一个线程池包含:
- 任务队列:存放生产者线程创建的任务
- 工作线程:取出任务队列中任务执行
- 构造函数
- 析构函数
- 添加任务函数
- 工作线程函数
1. 任务队列和工作线程
任务队列使用一个手动实现的阻塞队列来实现;
工作线程使用一个线程vector
来实现。
BlockingQueuePro<std::function<void()>> task_queue_; // 任务队列
std::vector<std::thread> workers_; // 工作线程列表
工作线程函数是一个不断循环的函数,从任务队列中取出任务并执行
// 工作线程函数
void Worker() {while (true) {std::function<void()> task;if (!task_queue_.Pop(task))break;task(); // 执行任务}
}
2. 构造和析构函数
构造函数传入一个整数作为线程池最大线程数,然后创建该数量的线程
// 构造函数
explicit ThreadPool(int num_threads) {for (size_t i = 0; i < num_threads; i++) {workers_.emplace_back([this] { Worker(); });}
}
析构函数将阻塞队列设置为非阻塞模型,并阻塞当前线程等待所有工作线程执行完毕
// 析构函数
~ThreadPool() {task_queue_.Cancel();for (auto &worker : workers_) {if (worker.joinable()) {worker.join();}}
}
3. 添加任务函数
Post函数传入一个可调用对象和参数,将可调用对象和参数绑定之后加入到工作队列中。
// 添加任务
template <typename F, typename... Args>
void Post(F &&f, Args &&...args) {auto task = std::bind(std::forward<F>(f), std::forward<Args>(args)...);task_queue_.Push(task);
}
4. 完整代码
class ThreadPool {
public:// 构造函数explicit ThreadPool(int num_threads) {for (size_t i = 0; i < num_threads; i++) {workers_.emplace_back([this] { Worker(); });}}// 析构函数~ThreadPool() {task_queue_.Cancel();for (auto &worker : workers_) {if (worker.joinable()) {worker.join();}}}// 添加任务template <typename F, typename... Args>void Post(F &&f, Args &&...args) {auto task = std::bind(std::forward<F>(f), std::forward<Args>(args)...);task_queue_.Push(task);}private:// 工作线程函数void Worker() {while (true) {std::function<void()> task;if (!task_queue_.Pop(task))break;task(); // 执行任务}}BlockingQueuePro<std::function<void()>> task_queue_; // 任务队列std::vector<std::thread> workers_; // 工作线程列表
};
三、阻塞队列实现
阻塞队列是一种特殊的队列,同样遵循“先进先出”的原则,支持入队操作和出队操作。在此基础上,阻塞队列会在队列已满或队列为空时陷入阻塞,使其成为一个线程安全的数据结构,它具有如下特性:
- 当队列已满时,继续入队列就会阻塞,直到有其他线程从队列中取走元素。
- 当队列为空时,继续出队列也会阻塞,直到有其他线程向队列中插入元素。
(引用参考:阻塞队列(超详细易懂)-CSDN博客)
1. 基础队列
生产者和消费者共用一个队列和互斥锁
- 当队列为空时,使工作线程进入休眠。
- 当队列被设置为非阻塞时,队列任务为空会使工作线程结束
源码:
template <typename T>
class BlockingQueue {
public:BlockingQueue(bool nonblock = false) : nonblock_(nonblock) {}// 添加任务void Push(const T &task) {std::lock_guard<std::mutex> lock(mutex_);queue_.push(task);not_empty_.notify_one(); // 通知一个等待的线程}// 获取任务bool Pop(T &task) {std::unique_lock<std::mutex> lock(mutex_);not_empty_.wait(lock, [this] { return !queue_.empty() || nonblock_; });if (queue_.empty()) return false; task = queue_.front();queue_.pop();return true;}// 解除阻塞当前队列的线程void Cancel() {std::lock_guard<std::mutex> lock(mutex_);nonblock_ = true; // 设置为非阻塞状态not_empty_.notify_all(); // 通知所有等待的线程}private:bool nonblock_; // 是否为非阻塞模式std::mutex mutex_; // 互斥锁std::condition_variable not_empty_; // 条件变量,队列为空时线程休眠std::queue<T> queue_; // 任务队列
};
2. 升级版队列
生产者和消费者有各自的任务队列和互斥锁
-
当消费者队列为空时,会尝试与生产者队列交换
-
若交换中生产者队列为空,使工作线程进入休眠;
-
若队列被设置为非阻塞,生产者队列为空,交换后消费者队列仍为空,此时会结束工作线程。
-
源码:
// 升级版队列,多生产者和多消费者
template <typename T>
class BlockingQueuePro {
public:BlockingQueuePro(bool nonblock = false) : nonblock_(nonblock) {}// 添加任务void Push(const T &task) {std::lock_guard<std::mutex> lock(producer_mutex_);producer_queue_.push(task);not_empty_.notify_one(); // 通知一个等待的线程}// 获取任务bool Pop(T &task) {std::unique_lock<std::mutex> lock(consumer_mutex_);// 如果消费者队列为空,尝试交换生产者队列if (consumer_queue_.empty() && SwapQueue_() == 0) {return false; // 如果交换后仍然为空,则返回false}task = consumer_queue_.front();consumer_queue_.pop();return true;}// 解除阻塞当前队列的线程void Cancel() {std::lock_guard<std::mutex> lock(producer_mutex_);nonblock_ = true; // 设置为非阻塞状态not_empty_.notify_all(); // 通知所有等待的线程}private:// 交换生产者队列到消费者队列size_t SwapQueue_() {std::unique_lock<std::mutex> lock(producer_mutex_);not_empty_.wait(lock, [this] { return !producer_queue_.empty() || nonblock_; });std::swap(producer_queue_, consumer_queue_); // 交换队列return consumer_queue_.size(); // 返回新的消费者队列大小}bool nonblock_; // 是否为非阻塞模式std::mutex producer_mutex_; // 生产者互斥锁std::mutex consumer_mutex_; // 消费者互斥锁std::condition_variable not_empty_; // 条件变量,队列为空时线程休眠std::queue<T> producer_queue_; // 生产者任务队列std::queue<T> consumer_queue_; // 消费者任务队列
};
四、测试代码
- 任务函数
Task()
:线程池中工作线程需要执行的任务 - 生产者函数
Producer()
:将num_tasks
个任务添加到线程池中 - 生产者线程
producers
:包含多个生产者,同时并行生成任务到线程池中 - 等待生产者线程完成任务生成
- 等待线程池执行完所有生成的任务
#include <iostream>
#include <thread>
#include <vector>
#include <atomic>
#include <chrono>#include "threadpool.h"// 全局计数器,统计任务完成的数量
std::atomic<int> task_counter(0);// 任务函数
void Task(int id) {std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟任务处理时间std::cout << "Task " << id << " executed by thread " << std::this_thread::get_id() << std::endl;task_counter++;
}// 生产者函数
void Producer(ThreadPool &pool, int producer_id, int num_tasks) {for (int i = 0; i < num_tasks; i++) {int task_id = producer_id * 1000 + i; // 生成唯一任务IDpool.Post(Task, task_id);std::cout << "Producer " << producer_id << " posted task " << task_id << std::endl;}
}int main() {const int num_producers = 3; // 生产者数量const int tasks_per_producer = 5; // 每个生产者生成的任务数量const int num_threads = 4; // 线程池中的线程数量ThreadPool pool(num_threads); // 创建线程池// 启动多个生产者线程std::vector<std::thread> producers;for (int i = 0; i < num_producers; i++) {producers.emplace_back(Producer, std::ref(pool), i, tasks_per_producer);}// 等待所有生产者完成for (auto &producer : producers) {producer.join();}// 等待一段时间以确保所有任务都被处理完while (task_counter < num_producers * tasks_per_producer) {std::this_thread::sleep_for(std::chrono::milliseconds(100));}std::cout << "Total tasks executed: " << task_counter.load() << std::endl;return 0;
}
五、相关问题
-
条件变量与线程同步问题
C++ 条件变量:wait、wait_for、wait_until_c++ 条件变量 wait-CSDN博客
-
虚假唤醒问题
-
一般为操作系统层面的原因导致的
- 实现优化:
操作系统或条件变量的底层实现(如 Linux 的futex
)为了提高性能,允许在未收到信号时唤醒线程。例如:- 内核可能在处理信号时意外唤醒线程。
- 多核 CPU 竞争资源时,硬件层面的竞争可能导致唤醒。
- 设计妥协:
允许虚假唤醒可以简化条件变量的实现,同时减少某些场景下的唤醒延迟。
- 实现优化:
-
解决方法
// 循环检查 while (condition) {cond.wait(lock); } // 谓词 cond.wait(lock, [](return ready));
-
-
引用包装
【C++】引用包装(std::ref与std::cref)-CSDN博客
六、其他实现方式
progschj/ThreadPool: A simple C++11 Thread Pool implementation
配合以下内容食用:
C++知识点记录-CSDN博客
相关文章:

C++八股 —— 手撕线程池
文章目录 一、背景二、线程池实现1. 任务队列和工作线程2. 构造和析构函数3. 添加任务函数4. 完整代码 三、阻塞队列实现1. 基础队列2. 升级版队列 四、测试代码五、相关问题六、其他实现方式 来自:华为C一面:手撕线程池_哔哩哔哩_bilibili 华为海思&am…...

RPA如何支持跨平台和跨浏览器的自动化
RPA,即机器人流程自动化(Robotic Process Automation),正日益成为企业实现业务流程高效自动化的关键技术。在复杂的数字化环境中,跨平台和跨浏览器的自动化需求极为迫切,RPA 通过多种技术手段和策略来满足这…...

【笔记】Windows 成功部署 Suna 开源的通用人工智能代理项目部署日志
#工作记录 本地部署运行截图 kortix-ai/suna: Suna - 开源通用 AI 代理 项目概述 Suna 是一个完全开源的 AI 助手,通过自然对话帮助用户轻松完成研究、数据分析等日常任务。它结合了强大的功能和直观的界面,能够理解用户需求并提供结果。其强…...
关于ffplay在macos上运行奔溃的问题
这个问题大概是由于 MacOS 的问题引起的,奔溃的地方在 SDL2 的代码中,如果直接使用 brew 安装 SDL2就会遇到这个问题,所以需要修改 SDL2源码然后再编译安装。 我这里采用的是 origin/release-2.28.x 分支,修改部分如下࿱…...

Linux531rsync定时同步 再回忆
rsync定时同步 环境配置 关闭防火墙,selinux systemctl stop firewalld systemctl disable firewall setenforce 0 cat /etc/selinux/configpei SELINUXdisable设置主机名 systemctl set-hostname code systemctl set-hostname backup设置静态IP rsync由于要设…...
Elasticsearch 分析器介绍
在 Elasticsearch 的世界里,构建高效搜索引擎的关键一环,便是透彻理解分析器(Analyzer)的工作机制。一个优秀的搜索引擎,能够精准地返回与用户查询紧密相关的文档,而这背后,正是分析器在默默发挥着核心作用。它不仅负责处理待索引的文档,还在用户发起查询时,智能评估哪…...

【KWDB 创作者计划】_探秘浪潮KWDB数据库:从时间索引到前沿技术
探秘浪潮KWDB数据库:从时间索引到前沿技术 文章目录 探秘浪潮KWDB数据库:从时间索引到前沿技术引言1.浪潮KWDB数据库时间索引深度解析1.1时间索引工作原理1.2时间索引创建与管理实践 2.浪潮KWDB数据库前沿产品技术纵览2.1多模融合存储引擎2.2就地计算技术…...

安卓逆向篇LSP 模块HOOK 添加技术绕过检测算法解密逻辑验证
前置解释: 0 、 Magisk : 是当前 Android 社区用来获取 root 权限的主流方式开源工具 1 、 LSP 框架: XPosed 框架因只支持安卓 8 及以下,故高版本应使用 MagiskLSPosed 2 、 HOOK 技术: 钩子技术&…...
【SQL】关键字
ORDER BY ORDER BY(排序) 语句可以按照一个或多个列的值进行升序(ASC)或降序(DESC)排序。 MAX / MIN MAX() 函数返回一组值中的最大值。这个函数常用于数字字段,但也可以用于文本字段来找出按字典顺序最后的元素。 …...

第一节 51单片机概述
目录 一、单片机系统组成 (一)、单片机硬件系统 (二)单片机的软件系统 二、STC89C52单片机 (1)、基本信息 (2)、命名规则 (3)、单片机内部结构图 &am…...

Google car key:安全、便捷的汽车解锁新选择
有了兼容的汽车和 Android 手机,Google car key可让您将Android 手机用作车钥匙。您可以通过兼容的 Android 手机锁定、解锁、启动汽车并执行更多功能。但是,Google car key安全吗?它是如何工作的?如果我的手机电池没电了怎么办&a…...

720全景展示:VR全景的技术原理及应用
VR720全景展示:技术原理及应用探索 720全景技术,作为当前全球范围内迅速崛起流行的视觉新技术,为用户带来了全新的真实现场感和交互式的体验。凭借全方位、无死角的视觉展示特性,在VR(虚拟现实)领域中得到…...

定制一款国密浏览器(13):预置国密根证书到浏览器
由于国密算法没有得到国外的认可,所以 Chromium、Firefox 等浏览器均不支持国密算法。即使我们修改了 Chromium 的源码,增加了国密算法的支持,但还不能在浏览器中正常使用。因为这涉及到证书的信任问题,国密证书都是国内厂商签发的,国密根证书并没有集成到系统和浏览器中。…...

PowerBI企业运营分析——线性回归销售预测
PowerBI企业运营分析——线性回归销售预测 欢迎来到Powerbi小课堂,在竞争激烈的市场环境中,企业运营分析平台成为提升竞争力的核心工具。 该平台通过整合多源数据,实现关键指标的实时监控,从而迅速洞察业务动态,精准…...
大模型运维过程中常见的一些操作
1. 模型部署与环境配置 基础设施准备:部署 GPU 集群、TPU 等专用硬件,配置分布式计算环境(如 Kubernetes)。推理服务搭建:使用 Triton Inference Server、TensorFlow Serving 等框架部署模型,优化批处理和…...
C# 关于闭包与多线程结合使用
开头先看一篇文章:【转】编写高质量代码改善C#程序的157个建议——建议75:警惕线程不会立即启动 - 指间的徘徊 - 博客园d 摘抄: static int _id 0; static void Main() { for (int i 0; i < 10; i, _id) { Thread t new Thread…...

LangFuse:开源LLM工程平台的革新实践
文章目录 一 架构设计与技术栈二 增强型监控能力三 提示词工程支持(新增)四 性能优化实践五 LangFuse部署(docker)和代码集成5.1 LangFuse平台部署5.2 LangFuse代码集成和检测体验 一 架构设计与技术栈 LangFuse采用模块化架构设…...

新视角!经济学顶刊QJE用文本分析探究新技术扩散
美国圣路易斯联邦储备银行Aakash Kalyani、美国斯坦福大学与国家经济研究局Nicholas Bloom、英国伦敦商学院Marcela Carvalho和其合作者们共同研究的“The Diffusion of New Technologies(新技术的扩散)”在顶刊The Quarterly Journal of Economics中发表…...
微信小程序返回上一页监听
本文实现的是微信小程序在返回上一页时获取通知并自定义业务。 最简单的实现: 使用 wx.enableAlertBeforeUnload() 优点:快速接入 缺点:手势不能识别、无法自定义弹窗内容(仅询问) 方法二: page-conta…...

5月31日day41打卡
简单CNN 知识回顾 数据增强卷积神经网络定义的写法batch归一化:调整一个批次的分布,常用与图像数据特征图:只有卷积操作输出的才叫特征图调度器:直接修改基础学习率 卷积操作常见流程如下: 1. 输入 → 卷积层 → Batch…...
“粽”览全局:分布式系统架构与实践深度解析(端午特别版)
第一部分:引言——技术世界的“端午”第二部分:分布式系统概述——粽子节点初探第三部分:核心技术详解——技术“粽子”大解构 粽叶篇:通信协议糯米篇:一致性算法馅料篇:任务调度与计算包扎篇:系…...

STM32G4 电机外设篇(一) GPIO+UART
目录 一、STM32G4 电机外设篇(一) GPIOUART1 GPIO1.1 STM32CUBEMX 配置以及Keil代码1.2 代码和实验现象 2 UART2.1 STM32CUBEMX 配置以及Keil代码2.2 代码和实验现象 附学习参考网址欢迎大家有问题评论交流 (* ^ ω ^) 一、STM32G4 电机外设篇࿰…...
代理IP在云计算中的应用:技术演进与场景实践
一、技术融合的必然性:代理IP与云计算的协同效应 在数字化转型的浪潮中,云计算已成为企业IT架构的核心底座,而代理IP技术则作为网络访问的关键基础设施,两者在技术演进路径上呈现出深度融合的趋势。云计算的弹性资源池与代理IP的…...

Lua 的速度为什么比 Python 快
Lua 的执行速度通常比 Python 快,主要原因在于其解释器设计轻量、虚拟机效率高、内存管理策略更为精简,以及语言本身对动态特性的控制较严。其中,Lua 使用了 register-based 的虚拟机架构,而 Python(CPython࿰…...

【iOS】方法交换
方法交换 method-swizzling是什么相关API方法交换的风险method-swizzling使用过程中的一次性问题在当前类中进行方法交换类方法的方法交换 方法交换的应用 method-swizzling是什么 method-swizzling的含义是方法交换,他的主要作用是在运行的时候将一个方法的实现替…...
跑步相关术语解释
老婆一直问我PB是啥意思,写个文章解释下。 1. 成绩类 PB (Personal Best):个人最好成绩,指在特定距离(如10K、全马)中的最快完赛时间 。PW (Personal Worst)࿱…...

数据结构:线性表的基本操作与链式表达
个人主页 文章专栏 成名之作——赛博算命之梅花易数的Java实现 陆续回三中,忘回漏回滴滴~感谢各位大佬的支持 一.线性表的定义和基本操作 1.1定义 线性表是具有相同数据类型的n个数据元素的有序数列,n为表长 第一个元素叫表头元素,除了他…...

C++:设计模式--工厂模式
更多内容:XiaoJ的知识星球 目录 1.简单工厂模式1.1 简单工厂1.2 实现步骤1.3 实现代码1.4 优缺点 2.工厂模式2.1 工厂模式2.2 实现步骤2.3 实现代码2.4 优缺点 3.抽象工厂模式3.1 抽象工厂模式3.2 实现步骤3.3 实现代码3.4 优缺点 1.简单工厂模式 . 1.1 简单工厂 …...
【前端优化】使用speed-measure-webpack-plugin分析前端运行、打包耗时,优化项目
记录下 项目安装speed-measure-webpack-plugin 打包分析: 修改vue.config.js文件 speed-measure-webpack-plugin进行构建速度分析,需要包裹整个 configureWebpack 配置 const originalConfig {publicPath: /,assetsDir: assets,parallel: true,devS…...

国产化Excel处理组件Spire.XLS教程:如何使用 C# 将 Excel(XLS 或 XLSX)文件转换为 PDF
Excel 是常见的数据处理与呈现工具,但直接共享 Excel 文件可能面临格式错乱、兼容性不足或数据泄露的风险。为了保证文档在不同平台和终端上的稳定展示,开发者常常需要将 Excel 文件转换为 PDF 格式。 本文将详细介绍如何使用 C#和.NET Excel 库——Spi…...