现代 C++ 异步编程:从零实现一个高性能 ThreadPool (C++20 深度实践)
在高性能 C 开发中线程池是绕不开的核心基础设施。随着 C20 标准的普及我们能够以更简洁、更安全的方式实现一个生产级的线程池。本文将带你深度剖析一个基于std::jthread的线程池实现并探讨其背后的架构思考与内存管理机制。1. 核心代码实现 (C20 版)这个线程池利用了 C20 的 RAII 线程管理特性彻底告别了手动join的时代。#pragmaonce#includecondition_variable#includefunctional#includefuture#includememory#includemutex#includequeue#includestdexcept#includethread#includetype_traits#includeutility#includevectorclassThreadPool{public:explicitThreadPool(std::size_t threads){if(threads0){throwstd::invalid_argument(ThreadPool requires at least one worker);}workers.reserve(threads);for(std::size_t i0;ithreads;i){// std::jthread 会自动管理线程生命周期。//// jthread 析构时会// 1. request_stop()// 2. join()//// stop_token 是 worker 获取停止请求的方式。workers.emplace_back([this](std::stop_token st){// 不要写//// while (!st.stop_requested())//// 因为我们的策略是//// 收到 stop// ↓// 不再等待新任务// ↓// 把已经在队列中的任务执行完// ↓// worker 退出//// 所以这里使用无限循环由下面的逻辑决定什么时候退出。for(;;){std::functionvoid()task;{std::unique_lockstd::mutexlock(queue_mutex);// 等待两个条件之一//// 1. tasks 非空// 2. stop_token 收到 stop 请求//// condition_variable_any 支持 stop_token// 因此 request_stop() 可以唤醒这里的 worker。//// wait() 返回//// true - predicate 满足即 tasks 非空// false - stop_token 收到了 stop 请求boolokcondition.wait(lock,st,[this]{return!tasks.empty();});// stop 请求并且任务队列已经为空//// stop true// tasks.empty() true//// 说明没有任何任务需要继续处理可以退出。if(!oktasks.empty()){return;}// 到这里有两种可能//// 1. 正常收到任务// 2. 收到 stop但队列里还有任务//// 两种情况都需要取出任务执行。taskstd::move(tasks.front());tasks.pop();}// 非常重要//// 不要持有 queue_mutex 执行任务。//// 如果任务执行时间很长而 mutex 一直被占用// 其他 worker 就无法从任务队列取任务。//// 所以//// 加锁// ↓// 取任务// ↓// 解锁// ↓// 执行任务task();}});}}~ThreadPool()default;templateclassF,class...Argsautoenqueue(Ff,Args...args)-std::futurestd::invoke_result_tF,Args...{// 获取任务函数的返回类型。//// 例如//// int foo(int);//// enqueue(foo, 10)//// return_type intusingreturn_typestd::invoke_result_tF,Args...;// packaged_task 用于//// worker 执行任务// ↓// 保存返回值// ↓// future 获取返回值//// packaged_task 本身是 move-only。//// 所以这里使用 shared_ptr 管理它的生命周期。autotaskstd::make_sharedstd::packaged_taskreturn_type()(std::bind(std::forwardF(f),std::forwardArgs(args)...));// future 是调用者获取任务执行结果的方式。std::futurereturn_typeresulttask-get_future();{std::unique_lockstd::mutexlock(queue_mutex);// tasks 的统一类型是//// std::functionvoid()//// 不管用户提交的是//// int foo()// void bar()// std::string baz()//// 最终都包装成//// void()//// worker 不需要知道任务的真实类型。tasks.emplace([task]{(*task)();});}// mutex 已经释放之后再通知 worker。//// notify_one() 唤醒一个正在等待任务的 worker。condition.notify_one();returnresult;}private:/* * 成员变量的声明顺序非常重要。 * * C 对成员变量的析构顺序是 * * 声明顺序的逆序 * * 所以这里实际上会按照 * * workers * ↓ * tasks * ↓ * condition * ↓ * queue_mutex * * 的顺序析构。 * * workers 必须最后声明。 * * 原因 * * worker 线程里面会访问 * * queue_mutex * condition * tasks * * 因此必须保证 worker 全部结束之后 * 这些对象才开始析构。 */// 保护 tasks。std::mutex queue_mutex;// condition_variable_any 支持 stop_token。std::condition_variable_any condition;// 等待执行的任务。std::queuestd::functionvoid()tasks;// 必须最后声明。//// 析构时 workers 最先被析构//// ~jthread()// ↓// request_stop()// ↓// worker 被唤醒// ↓// worker 退出// ↓// join()//// 等所有 worker 结束以后// tasks / condition / queue_mutex 才会析构。std::vectorstd::jthreadworkers;};2. 深度剖析为什么不直接存packaged_task这是初学者常有的疑问既然std::packaged_task已经封装了任务为什么队列里存的是std::functionvoid()类型擦除 (Type Erasure)std::packaged_taskR(Args...)是一个强类型模板它的类型包含了返回值R。如果任务 A 返回int任务 B 返回string它们的类型是不同的。std::queue只能存储相同类型的对象。解决方案我们利用std::functionvoid()作为“通用信封”。在enqueue内部我们写了一个 Lambda 表达式[task](){ (*task)(); }。无论原始任务返回什么这个 Lambda 永远是void()类型。这在设计模式中被称为类型擦除它抹平了不同任务之间的差异。3. 内存管理智能指针的“接力”在异步环境下最怕的是“任务还没跑对象先没了”。本实现通过std::shared_ptr和 Lambda 捕获完美解决了生存周期问题。诞生在enqueue里通过make_shared创建任务。此时计数为 1。接力Lambda 表达式通过按值捕获[task]。这意味着shared_ptr被拷贝了一份存进了 Lambda 闭包中。此时计数为 2。入队Lambda 被存入std::function并进入队列。即使enqueue函数返回闭包依然拉着shared_ptr。销毁工作线程取出并执行完任务后std::function被销毁引用计数降为 0任务对象内存自动释放。这种“引用计数闭包捕获”的机制保证了任务只要在队列中内存就绝对安全。4. C20 的技术红利std::jthread 与 RAII传统的std::thread在析构时如果不join或detach会导致进程异常退出。std::jthread引入了 RAII资源获取即初始化机制它在析构时会自动发出停止请求并等待线程结束使得线程池的析构函数极其简洁。协作式中断 (stop_token)通过std::stop_token我们不再需要手动维护一个bool stop标志。condition.wait(lock, st, pred)更是神来之笔当jthread准备停止时它会自动唤醒所有阻塞在条件变量上的线程。这比以前手动notify_all()要健壮得多。5. 如何使用intmain(){ThreadPoolpool(4);// 提交带返回值的异步任务autofuturepool.enqueue([](intx){returnx*x;},10);// 获取结果 (会阻塞直到任务完成)std::coutResult: future.get()std::endl;return0;}6. 总结实现一个线程池不难但实现一个既类型安全又内存无忧的线程池需要对 C 的底层机制有深刻理解。std::functionvoid()解决了异质任务的存储问题。std::shared_ptr解决了异步任务的生命周期问题。std::jthread解决了线程资源的回收问题。这个短小精悍的实现正是 C 现代化的魅力所在。版权声明本文采用 CC BY-SA 4.0 协议转载请注明出处。

相关新闻

开发者技术搜索安全指南:从官方文档到代码验证的全流程实践

开发者技术搜索安全指南:从官方文档到代码验证的全流程实践

最近在技术社区看到不少关于“最可怕的搜索引擎”的讨论,很多开发者,尤其是刚入门的新手,在寻找技术解决方案时,可能会误入一些存在安全风险、隐私泄露或提供恶意代码的网站。本文并非要讨论某个具体的搜索引擎,而是想…

2026/10/13 20:37:07 阅读更多 →
营销智脑V3 产品迭代更新全景图:从V6.0到V6.2,AI营销平台的成长之路

营销智脑V3 产品迭代更新全景图:从V6.0到V6.2,AI营销平台的成长之路

3个月 13个版本 95条更新记录 —— 一个AI营销平台从"能用"到"好用"再到"专业"的完整进化史。前言 营销智脑V3框架打造花了3年多时间,近期3个月爆发式更新。 从最初的多智能体协作,到全域矩阵分发,再到今天的…

2026/10/11 7:14:27 阅读更多 →
从利利博士固态智能警告看AI安全:目标对齐与约束编码的工程实践

从利利博士固态智能警告看AI安全:目标对齐与约束编码的工程实践

在探索人工智能与人类未来的交叉领域时,我们常常会回溯历史,从那些富有远见的思考中汲取灵感。约翰C利利(John C. Lilly)博士在1978年提出的关于“固态智能”与人类命运的论述,便是这样一个跨越时代的深刻洞见。对于从…

2026/10/7 16:53:54 阅读更多 →

最新新闻

BL0940电能计量芯片设计实战:从硬件采样到量产校准

BL0940电能计量芯片设计实战:从硬件采样到量产校准

简介:面向硬件与嵌入式开发者的BL0940电能计量专用电路设计资料,涵盖芯片外围电路、代码实现与完整工程文件。资料围绕BL0940在智能电网、智能家居、工业自动化等场景的高精度电能测量需求,重点讲解模拟前端信号调理、数字信号处理及RS485/Mo…

2026/10/13 20:38:51 阅读更多 →
SYCL编码实践与编译设计:从缓冲区内核到设备二进制

SYCL编码实践与编译设计:从缓冲区内核到设备二进制

SYCL这套东西,我前后花了不少时间才把它从“会写一点点”掰扯到“敢说懂了一层”的状态。前两篇笔记里,我把队列、处理器、缓冲区这些基础组件过了一遍,顺手整理了几个能跑通的最小例子。这篇是第三篇,我打算把重心换一下&#xf…

2026/10/13 20:38:51 阅读更多 →
AI日报制作全流程:从信息源筛选到自动化辅助的工程实践

AI日报制作全流程:从信息源筛选到自动化辅助的工程实践

1. 一份AI日报的诞生逻辑:为什么值得认真做每天早上花十五分钟翻一遍行业动态,这个习惯我坚持了快三年。最开始只是自己看,后来顺手整理成几条发在群里,再后来有朋友说“你干脆做成日报吧,省得我们到处翻”。于是就有了…

2026/10/13 20:38:51 阅读更多 →
PIPE4.3.0 Petri网建模仿真实战:从建模到验证避坑指南

PIPE4.3.0 Petri网建模仿真实战:从建模到验证避坑指南

简介:PIPE 4.3.0 是一款基于 Petri 网的可视化建模与仿真工具,面向软件工程、计算机科学、流程管理等领域的研究者与工程师。它通过图形化的库所(Place)、变迁(Transition)和弧线构建系统模型,支…

2026/10/13 20:38:51 阅读更多 →
Firebase-Gantt:DHTMLX甘特图实时协同的轻量级适配方案

Firebase-Gantt:DHTMLX甘特图实时协同的轻量级适配方案

简介:这是一份面向Web前端开发者与实时协作应用构建者的JavaScript工具库,专为将dhtmlxGantt甘特图组件无缝集成至Firebase后端而设计。它解决了传统甘特图数据需手动同步、难以实现实时协同编辑与离线操作的痛点,适用于项目管理SaaS、团队任…

2026/10/13 20:38:51 阅读更多 →
学编程前先做一件事:用“问题倒推法”明确目标,让学习效率翻倍

学编程前先做一件事:用“问题倒推法”明确目标,让学习效率翻倍

很多人问我:编程到底怎么学才能不痛苦?我带过的新人不少,也看过很多自学者的成长路径。有一个人让我印象特别深,他学某个语言学了大半年,语法书翻了三遍,视频课存了几十个G,收藏夹里堆满了各种教…

2026/10/13 20:37:49 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →