当前位置: 首页 > article >正文

C++ 笔记 高级线程同步原语与线程池实现

在std::thread基础上C11 还提供了std::condition_variable条件变量和std::atomic原子变量两大高级同步原语分别解决 “线程间协作通知” 和 “无锁数据竞争” 问题而线程池则是对std::thread的高层封装通过预创建线程池避免频繁创建 / 销毁线程的开销是多线程开发的核心工具。本文将从底层原理、代码示例到完整实现系统拆解这三大知识点。一、std::condition_variable线程间的条件同步1.1 核心作用与原理std::condition_variable用于线程间的协作通知一个线程等待某个条件成立而阻塞另一个线程在条件成立时通知阻塞线程继续执行。它必须与std::unique_lockstd::mutex配合使用 ——mutex 保护共享条件条件变量负责线程的阻塞与唤醒二者结合解决 “忙等待Busy Wait” 问题避免线程空转浪费 CPU。1.2 关键函数表格函数作用wait(lock)阻塞当前线程直到被通知自动释放锁被唤醒后重新获取锁wait(lock, pred)带谓词的wait阻塞直到被通知且pred()为true自动处理虚假唤醒notify_one()唤醒一个等待的线程notify_all()唤醒所有等待的线程1.3 经典示例生产者 - 消费者模型这是条件变量最典型的应用场景生产者线程生成数据放入队列消费者线程从队列取数据处理队列满时生产者阻塞队列空时消费者阻塞。#include iostream #include thread #include mutex #include condition_variable #include queue #include chrono std::queueint data_queue; // 共享数据队列 std::mutex mtx; // 保护队列的互斥锁 std::condition_variable cv; // 条件变量 const int MAX_QUEUE_SIZE 5; // 队列最大容量 // 生产者线程生成数据放入队列 void producer() { for (int i 1; i 10; i) { { std::unique_lockstd::mutex lock(mtx); // 队列满时阻塞等待消费者取数据谓词防止虚假唤醒 cv.wait(lock, []() { return data_queue.size() MAX_QUEUE_SIZE; }); data_queue.push(i); std::cout [生产者] 放入数据: i 队列大小: data_queue.size() \n; } cv.notify_one(); // 通知一个消费者线程 std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟生产耗时 } } // 消费者线程从队列取数据处理 void consumer(int id) { while (true) { int data; { std::unique_lockstd::mutex lock(mtx); // 队列空时阻塞等待生产者放数据同时检查是否生产结束 cv.wait(lock, []() { return !data_queue.empty(); }); data data_queue.front(); data_queue.pop(); std::cout [消费者 id ] 取出数据: data 队列大小: data_queue.size() \n; } cv.notify_one(); // 通知一个生产者线程 std::this_thread::sleep_for(std::chrono::milliseconds(200)); // 模拟消费耗时 if (data 10) break; // 生产结束退出循环 } } int main() { std::thread prod(producer); std::thread cons1(consumer, 1); std::thread cons2(consumer, 2); prod.join(); cons1.join(); cons2.join(); return 0; }1.4 关键细节虚假唤醒wait()可能在没有被notify_one()/notify_all()调用时被唤醒称为 “虚假唤醒”因此必须用带谓词的wait谓词用于验证条件是否真正成立确保线程安全。二、std::atomic无锁原子操作2.1 核心作用与原理std::atomic提供无锁的原子操作底层依赖硬件的原子指令如 x86 的LOCK前缀比std::mutex更轻量适合简单的共享数据如计数器、标志位避免了锁的开销和死锁风险。2.2 基本用法#include iostream #include thread #include atomic #include vector // 1. 原子计数器无锁实现多线程安全 std::atomicint atomic_count(0); // 对比非原子计数器会有数据竞争 int unsafe_count 0; void atomic_increment() { for (int i 0; i 10000; i) { atomic_count; // 原子自增操作底层是硬件原子指令 } } void unsafe_increment() { for (int i 0; i 10000; i) { unsafe_count; // 非原子操作多线程下会出错 } } int main() { // 测试原子计数器 std::vectorstd::thread threads1; for (int i 0; i 10; i) { threads1.emplace_back(atomic_increment); } for (auto t : threads1) t.join(); std::cout [原子计数器] 最终结果: atomic_count 期望 100000实际一致\n; // 测试非原子计数器 std::vectorstd::thread threads2; for (int i 0; i 10; i) { threads2.emplace_back(unsafe_increment); } for (auto t : threads2) t.join(); std::cout [非原子计数器] 最终结果: unsafe_count 期望 100000实际可能更小\n; // 2. 原子标志位用于线程间的停止信号 std::atomicbool stop_flag(false); std::thread worker([stop_flag]() { while (!stop_flag) { std::cout [工作线程] 运行中...\n; std::this_thread::sleep_for(std::chrono::milliseconds(500)); } std::cout [工作线程] 收到停止信号退出\n; }); std::this_thread::sleep_for(std::chrono::seconds(2)); stop_flag true; // 原子设置标志位通知工作线程停止 worker.join(); return 0; }2.3 内存序简介进阶std::atomic支持不同的内存序Memory Order用于控制多线程下的指令重排和可见性默认是std::memory_order_seq_cst顺序一致性最安全但性能稍低其他内存序如memory_order_relaxed、memory_order_acquire、memory_order_release可用于优化性能但需谨慎使用容易出错。三、线程池高效的线程管理3.1 为什么需要线程池直接使用std::thread有两个核心问题频繁创建 / 销毁线程开销大线程创建需要分配栈空间、内核态切换销毁也需要回收资源短任务场景下开销甚至超过任务本身。线程数量不可控无限制创建线程会导致系统资源耗尽、CPU 调度开销过大。线程池的核心思想是预创建一组线程固定数量或动态调整将任务提交到任务队列线程池中的线程循环从队列取任务执行避免了频繁创建 / 销毁线程的开销同时控制了线程数量。3.2 线程池的核心组成一个完整的线程池包含以下部分线程数组预创建的工作线程集合。任务队列存储待执行任务的队列需用 mutex 和条件变量保护。同步机制std::mutex保护任务队列std::condition_variable通知工作线程有新任务。任务提交接口支持提交任意可调用对象函数、lambda、成员函数等并返回std::future获取任务结果。3.3 完整实现一个可提交任务、获取结果的线程池#include iostream #include thread #include mutex #include condition_variable #include queue #include vector #include functional #include future #include memory #include stdexcept class ThreadPool { public: // 构造函数创建指定数量的工作线程 ThreadPool(size_t num_threads) : stop(false) { for (size_t i 0; i num_threads; i) { // 每个工作线程循环执行从任务队列取任务并执行 workers.emplace_back([this]() { while (true) { std::functionvoid() task; { std::unique_lockstd::mutex lock(this-queue_mutex); // 阻塞直到线程池停止 或 有新任务 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(); } }); } } // 析构函数停止所有线程 ~ThreadPool() { { std::unique_lockstd::mutex lock(queue_mutex); stop true; // 设置停止标志 } condition.notify_all(); // 唤醒所有工作线程 for (std::thread worker : workers) { worker.join(); // 等待所有线程退出 } } // 禁止拷贝和移动 ThreadPool(const ThreadPool) delete; ThreadPool operator(const ThreadPool) delete; // 核心接口提交任务返回 std::future 获取结果 templateclass F, class... Args auto submit(F f, Args... args) - std::futuretypename std::result_ofF(Args...)::type { using return_type typename std::result_ofF(Args...)::type; // 将任务包装为 std::packaged_task支持获取返回值 auto task std::make_sharedstd::packaged_taskreturn_type()( std::bind(std::forwardF(f), std::forwardArgs(args)...) ); std::futurereturn_type res task-get_future(); { std::unique_lockstd::mutex lock(queue_mutex); // 线程池停止后禁止提交任务 if (stop) throw std::runtime_error(submit on stopped ThreadPool); // 将任务放入队列包装为 std::functionvoid() tasks.emplace([task]() { (*task)(); }); } condition.notify_one(); // 通知一个工作线程有新任务 return res; } private: std::vectorstd::thread workers; // 工作线程数组 std::queuestd::functionvoid() tasks; // 任务队列 std::mutex queue_mutex; // 保护任务队列的互斥锁 std::condition_variable condition; // 条件变量通知工作线程 bool stop; // 停止标志 }; // ------------------------------ 线程池使用示例 ------------------------------ // 示例1简单的无返回值任务 void print_task(int id) { std::cout [线程池] 执行任务 id 线程ID: std::this_thread::get_id() \n; std::this_thread::sleep_for(std::chrono::milliseconds(100)); } // 示例2有返回值的任务 int add_task(int a, int b) { std::this_thread::sleep_for(std::chrono::milliseconds(200)); return a b; } int main() { // 创建线程池4个工作线程 ThreadPool pool(4); std::cout [主线程] 线程池已启动工作线程数量: 4\n; // 提交10个无返回值任务 std::cout \n[主线程] 提交10个无返回值任务...\n; for (int i 0; i 10; i) { pool.submit(print_task, i 1); } // 提交3个有返回值任务用 std::future 获取结果 std::cout \n[主线程] 提交3个有返回值任务...\n; std::vectorstd::futureint futures; futures.push_back(pool.submit(add_task, 10, 20)); futures.push_back(pool.submit(add_task, 30, 40)); futures.push_back(pool.submit(add_task, 50, 60)); // 获取并打印有返回值任务的结果 std::cout \n[主线程] 等待有返回值任务完成...\n; for (size_t i 0; i futures.size(); i) { std::cout [主线程] 任务 i 1 结果: futures[i].get() \n; } // 主线程等待一段时间让无返回值任务执行完毕 std::this_thread::sleep_for(std::chrono::seconds(2)); std::cout \n[主线程] 所有任务执行完毕线程池将自动析构\n; return 0; } // 线程池析构自动停止所有工作线程3.4 关键细节任务包装用std::packaged_task和std::bind将任意可调用对象包装为无参数的std::functionvoid()同时支持通过std::future获取返回值。线程安全任务队列的访问必须用std::mutex保护条件变量用于通知工作线程有新任务或线程池停止。析构安全析构函数中先设置stop标志再唤醒所有线程最后join()等待线程退出确保线程池安全销毁。四、总结与最佳实践std::condition_variable用于线程间协作通知必须与std::unique_lock配合优先用带谓词的wait()处理虚假唤醒典型场景是生产者 - 消费者模型。std::atomic用于无锁原子操作比mutex轻量适合简单共享数据计数器、标志位默认内存序memory_order_seq_cst最安全进阶可按需调整。线程池是多线程开发的核心工具通过预创建线程避免频繁创建 / 销毁开销核心是 “任务队列 工作线程”支持提交任意任务并获取结果适合大量短任务场景。掌握这三大知识点能让你高效编写高性能、线程安全的 C 多线程程序。

相关文章:

C++ 笔记 高级线程同步原语与线程池实现

在 std::thread 基础上,C11 还提供了 std::condition_variable(条件变量) 和 std::atomic(原子变量) 两大高级同步原语,分别解决 “线程间协作通知” 和 “无锁数据竞争” 问题;而 线程池 则是对…...

《检验检测机构资质认定管理办法》解读,检测机构资质认定实操指南与合规要点

《检验检测机构资质认定管理办法》是由国家市场监督管理总局制定的部门规章,是检验检测机构资质认定工作的规范性文件,内容涵盖了资质认定条件和程序、技术评审管理、监督检查等方面。本文我们通过对《检验检测机构资质认定管理办法》的解读,…...

终极HLS流媒体下载器:一键保存加密视频的完整指南

终极HLS流媒体下载器:一键保存加密视频的完整指南 【免费下载链接】m3u8_downloader 项目地址: https://gitcode.com/gh_mirrors/m3/m3u8_downloader 你是否曾遇到过这样的情况:在线课程视频无法下载复习,精彩直播回放无法收藏&#…...

SCH16T-K20陀螺仪、加速度计、惯性测量单元IMU、组合惯导系统

SCH16T-K20专为机器人、无人机和摄像头系统领域的OEM厂商以及需要安全关键型IMU的。 | SCH16T-K20专为机器人、无人机和摄像头系统领域的OEM厂商以及需要安全关键型IMU的IMU模块制造商和系统集成商而设计,SCH16T-K20提供市场居先的精度、机械稳健性和稳定性。SCH16T…...

大模型应用误区:RAG与垂域模型到底啥关系?老板必看!

本文深入解析了“垂域大模型”、“RAG”和“通用大模型”之间的关系,指出垂域大模型是针对特定行业进行深度优化的专家型模型,而RAG则是通过检索增强生成技术应用于通用大模型之上,属于通用模型的应用。文章强调RAG和垂域大模型在技术归属、底…...

雷军15小时一镜到底测SU7续航跑1313公里,撕下了汽车评测行业的遮羞布

昨天我刷到雷军15小时直播测SU7续航的时候,第一反应是:太拼了,一个CEO连续坐15小时车,中间不停播、不切镜头,就为了测个真实续航。最后结果出来,CLTC标称1200公里的SU7 Max,跑了1313公里还剩5%电…...

9 款免费测试管理系统对比:谁更适合中小企业和研发团队?

本文将深入对比 9 款免费测试管理工具与开源测试平台:PingCode、Worktile、Qase、Testiny、QA Touch、TestLink、Kiwi TCMS、Squash TM、Tuleap。一、预算有限团队选择免费测试管理工具时,先看什么 很多企业在找免费测试管理工具时,第一反应是…...

从命令行到IDE:OMNeT++ 4.6安装后,如何高效创建你的第一个网络仿真项目?

从命令行到IDE:OMNeT 4.6安装后高效创建首个网络仿真项目指南 当你第一次打开OMNeT IDE时,那种既兴奋又茫然的感觉我至今记忆犹新——满屏的菜单选项、陌生的术语、复杂的项目结构,让人不知从何下手。本文将带你跨越这个"新手墙"&a…...

SAP GUI 760环境下,ABAP Dialog Screen开发的5个新手常见坑及避坑指南

SAP GUI 760环境下ABAP Dialog Screen开发的5个新手常见坑及避坑指南 在SAP GUI 760环境下进行ABAP Dialog Screen开发时,新手开发者常常会遇到一些看似简单却令人头疼的问题。这些问题往往与新版GUI的特性、ABAP屏幕开发的特殊机制以及开发习惯有关。本文将深入剖析…...

计算机毕业设计:Python棉花种植生产智能监测与预测系统 Django框架 ARIMA算法 数据分析 可视化 爬虫 大数据 大模型(建议收藏)✅

博主介绍:✌全网粉丝10W,前互联网大厂软件研发、集结硕博英豪成立工作室。专注于计算机相关专业项目实战6年之久,选择我们就是选择放心、选择安心毕业✌ > 🍅想要获取完整文章或者源码,或者代做,拉到文章底部即可与…...

编写程序搭建社保医保代扣对账校验工具,核对智能代扣流水与缴费标准,自动筛查扣费误差漏扣错扣异常账单。

面向高校财务、HR、会计实训场景,用于批量核对代扣流水 vs 缴费标准,自动发现漏扣、错扣、金额异常。 一、实际应用场景描述 典型场景(高校 / 中小企业): - 某高校人事处每月为教职工代扣: - 养老保险 - 医…...

(claude code)最强skill everything-claude-code 技能完整指南

everything-claude-code 技能完整指南 本文档介绍 everything-claude-code 插件提供的所有技能(skills)及其用途。 一、核心开发流程 技能用途plan创建实施计划 - 新功能开发前先规划,分阶段拆解任务tdd测试驱动开发 - 先写测试再实现代码&…...

基于深度学习的YOLO11的河道垃圾识别 海洋垃圾检测与垃圾分类项目介绍

文章目录基于YOLOv8的河道及海洋垃圾检测与垃圾分类项目介绍一、YOLOv8简介二、项目背景与意义三、基于YOLOv8的垃圾检测与分类系统![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/2434d65e833b497ab5f750156f67c69e.png)四、数据集构建五、具体训练代码教程六、结论…...

敏芮芯途敏宝长高奶粉,助力敏宝长高,超 90%宝妈信赖的选择!

开篇引言在 2026 年,婴幼儿特医奶粉行业呈现出诸多显著趋势。随着生活环境等因素的变化,牛奶蛋白过敏宝宝群体逐年增加,家长们在为宝宝选奶时,更加看重产品的合规性与口感。特医配方朝着精细化分级的方向发展,易吸收护…...

推送通知实现长连接与消息队列

推送通知在现代应用中扮演着至关重要的角色,无论是社交媒体的即时消息、电商平台的订单提醒,还是金融应用的交易通知,都离不开高效稳定的推送机制。而长连接与消息队列作为实现推送通知的两大核心技术,能够确保消息的实时性和可靠…...

ArcMap转换坐标系

背景:我有一个tif文件,坐标系是WGS_1984_UTM_Zone_49N,不符合我的要求,我想转成GCS_WGS_1984坐标系, 有两种方法: 1、 2、 我用的是第二种方法,转换速度很快 在压缩参数上也要注意&#xff…...

前端三大核心技术语言

前端开发涉及的编程语言主要可分为核心标记/样式语言、核心脚本语言及其增强/替代方案,以及辅助/全栈语言。其核心生态、优势及典型应用场景对比如下: 语言类别具体语言核心定位与优势典型应用场景核心标记/样式语言HTML (HTML5)网页内容与结构的骨架&a…...

AI结对编程实测:减少47%代码评审时间的“黑暗技巧”——测试工程师的效能革命

在软件开发的效率竞赛中,代码评审环节往往扮演着“质量守门员”与“流程减速带”的双重角色。对于软件测试从业者而言,评审不仅是发现缺陷的最后一道防线,更是理解系统实现、设计验证策略的关键窗口。然而,传统评审模式高度依赖人…...

NCE外汇:指尖战场还是桌面指挥中心?深入对比移动端与桌面版交易体验

在快节奏的外汇市场,交易者如同战场上的将领,需要随时洞察瞬息万变的行情,及时下达精确指令。选择合适的交易平台——“武器”和“指挥所”,至关重要。NCE外汇为广大投资者提供了功能强大的桌面平台和灵活便捷的移动应用。两者并非…...

3分钟掌握微信聊天记录完整导出:WeChatMsg终极实战指南

3分钟掌握微信聊天记录完整导出:WeChatMsg终极实战指南 【免费下载链接】WeChatMsg 提取微信聊天记录,将其导出成HTML、Word、CSV文档永久保存,对聊天记录进行分析生成年度聊天报告 项目地址: https://gitcode.com/GitHub_Trending/we/WeCh…...

第3篇:封装——公有、私有与property

封装——公有、私有与property 封装是面向对象编程的核心概念之一,在Python中通过命名约定和特定机制实现。以下是逐步解析: 1. 封装的含义 封装包含两层含义: 数据与行为捆绑:对象同时包含数据(属性)和操作…...

如何修复 Flexbox 布局在移动端失效的问题:关键在于容器宽度与响应式约束

本文详解为何基于 Flexbox 构建的输入框组件在桌面端正常、却在移动端布局错乱,并给出精准修复方案——核心是为 .inputs 容器显式声明 width: 100%,同时补充 viewport 设置、弹性子项行为修正及移动端交互优化建议。 本文详解为何基于 flexbox 构建…...

手机设置手动代理后,小程序进不去,提示“运行环境加载失败2101”

问题分析在进行小程序测试时候,用到Fiddler或者Charles抓包,都要在手机设置手动代理配置后进行抓包。在手机配置手动代理后,手机没办法上网,所有小程序打不开。点击小程序,提示“运行环境加载失败2101”。怀疑是证书或…...

别再只用if-else了!用Simulink Stateflow Chart模块给你的算法加个‘状态’(附代码生成分析)

从条件分支到状态思维:用Simulink Stateflow重构复杂算法逻辑 在汽车电子和工业控制领域,工程师们常常需要处理多模态的系统行为。传统做法是用if-else或Switch模块搭建决策树,但当系统状态超过三个、状态转移条件涉及多个传感器输入时&#…...

Python基础-[面试]-救急知识速背

基础语法(15题)【基础语法】 问题:Python代码块是通过什么方式划分的? 答案:通过缩进(通常4个空格)划分代码块。【基础语法】 问题:Python中单行注释使用什么符号? 答案&…...

AI周报 | 算力涨价近半、融资965亿、AI开始像真人员工

日期:2026年4月13日—4月19日 本周最厉害的三件事: 1️⃣ 超级聪明的AI程序一个接一个发布,像比赛一样。 2️⃣ AI已经学会“自己动脑子、自己干活、自己记经验”了。 3️⃣ 全世界对“AI算力”(也就是AI的“脑力工厂”&#xff0…...

AVIF 与 PNG:下一代图像格式如何改变网页视觉与性能

随着互联网对高质量图像和快速加载速度的要求不断提高,图像格式也在不断进化。从早期的 JPEG、PNG,到如今逐渐普及的 WebP 和 AVIF,图像技术正在经历一场深刻的变革。 其中,AVIF 是近年来最受关注的新一代图像格式之一&#xff0…...

Session Startup:中描述的md文件是代码读取,还是 AI 操作?

Session Startup:文件是代码读取,还是 AI 操作? 核心结论:代码已经读取,Session Startup 只是声明。 🎯 直接答案 代码已经读取,Session Startup 只是声明。 📊 对比分析 项目 实际情况 谁读取文件? 代码,不是 AI 何时读取? 在 AI 启动前,系统构建 prompt 时 Se…...

初阶linux2( Linux 环境基础开发工具使用指南)

📚 目录(俏皮版) 🍳 一、软件管家 yum —— 做饭先备料 查看菜谱(软件包) 点菜安装 撤菜卸载 文件搬运工 rzsz ✍️ 二、编辑器 vim —— 键盘上的指尖芭蕾 三种核心模式 基本操作:进、写、退 正…...

imFile下载管理器:从零开始构建你的高效下载工作流

imFile下载管理器:从零开始构建你的高效下载工作流 【免费下载链接】imfile-desktop A full-featured download manager. 项目地址: https://gitcode.com/gh_mirrors/im/imfile-desktop 还记得那些焦急等待大文件下载的夜晚吗?当浏览器下载器卡在…...