C++ 11/14/17 线程资源同步对象
在 C/C 语言中直接使用操作系统提供的多线程资源同步 API 虽然功能强大但毕竟存在诸多限制且同样的代码却不能同时兼容 Windows 和 Linux 两个平台再者 C/C 这种传统语言的使用份额正在被 Java、python、go 等语言慢慢蚕食很大一部分原因是 C/C 这门编程语言在一些功能上缺少“完备性”如对线程同步技术的支持而这些功能在像 Java、python、go 中是标配。因此 C 11 标准新加入了很多现代语言标配的东西其中线程资源同步对象就是其中很重要的一部分。本小节将讨论 C 11 标准中新增的用于线程同步的std::mutex和std::condition_variable对象的用法有了它们我们就可以写出跨平台的多线程程序了。std::mutex 系列关于 mutex 的基本概念上文已经介绍过了这里不再赘述。C 11/14/17 中提供了如下 mutex 系列类型互斥量版本作用mutexC11最基本的互斥量timed_mutexC11有超时机制的互斥量recursive_mutexC11可重入的互斥量recursive_timed_mutexC11结合 timed_mutex 和 recursive_mutex 特点的互斥量shared_timed_mutexC14具有超时机制的可共享互斥量shared_mutexC17共享的互斥量这个系列的对象均提供了加锁lock、尝试加锁trylock和解锁unlock的方法我们以std::mutex为例来看一段示例代码#include iostream #include chrono #include thread #include mutex // protected by g_num_mutex int g_num 0; std::mutex g_num_mutex; void slow_increment(int id) { for (int i 0; i 3; i) { g_num_mutex.lock(); g_num; std::cout id g_num std::endl; g_num_mutex.unlock(); //sleep for 1 second std::this_thread::sleep_for(std::chrono::seconds(1)); } } int main() { std::thread t1(slow_increment, 0); std::thread t2(slow_increment, 1); t1.join(); t2.join(); return 0; }上述代码中创建了两个线程t1和t2在线程函数的 for 循环中调用 std::mutex.lock() 和 std::mutex.unlock() 对全局变量g_num进行保护。编译程序并输出结果如下[rootlocalhost testmultithread]# g -g -o mutex c11mutex.cpp -stdc0x -lpthread [rootlocalhost testmultithread]# ./mutex 0 1 1 2 0 3 1 4 1 5 0 6注意如果你在 Linux 下编译和运行程序在编译时你需要链接 pthread 库否则能够正常编译但是运行时程序会崩溃崩溃原因terminate called after throwing an instance of std::system_error what(): Enable multithreading to use std: Operation not permitted为了避免死锁std::mutex.lock()和std::mutex::unlock()方法需要成对使用但是如上文介绍的如果一个函数中有很多出口而互斥体对象又是需要在整个函数作用域保护的资源那么在编码时因为忘记在某个出口处调用std::mutex.unlock而造成死锁上文中推荐使用利用 RAII 技术封装这两个接口其实 C 11 标准也想到了整个问题因为已经为我们提供了如下封装互斥量管理版本作用lock_guardC11基于作用域的互斥量管理unique_lockC11更加灵活的互斥量管理shared_lockC14共享互斥量的管理scope_lockC17多互斥量避免死锁的管理我们这里以std::lock_guard为例void func() { std::lock_guardstd::mutex guard(mymutex); //在这里放被保护的资源操作 }mymutex 的类型是 std::mutex在guard对象的构造函数中会自动调用 mymutex.lock() 方法加锁当该函数出了作用域后调用guard对象时析构函数时会自动调用 mymutex.unlock() 方法解锁。注意mymutex 生命周期必须长于函数 func 的作用域很多人在初学这个利用 RAII 技术封装的 std::lock_guard 对象时可能会写出这样的代码//错误的写法这样是没法在多线程调用该函数时保护指定的数据的。 void func() { std::mutex m; std::lock_guardstd::mutex guard(m); //在这里放被保护的资源操作 }另外如果一个 std::mutex 对象已经调用了lock()方法再次调用时其行为是未定义的这是一个错误的做法。所谓“行为未定义”即在不同平台上可能会有不同的行为。#include mutex int main() { std::mutex m; m.lock(); m.lock(); m.unlock(); return 0; }实际测试时上述代码重复调用std::mutex.lock()方法在 Windows 平台上会引起程序崩溃。如下图所示上述代码在 Linux 系统上运行时会阻塞在第二次调用 std::mutex.lock() 处验证结果如下[rootlocalhost testmultithread]# g -g -o mutexlock mutexlock.cpp -stdc0x -lpthread [rootlocalhost testmultithread]# gdb mutexlock Reading symbols from /root/testmultithread/mutexlock...done. (gdb) r Starting program: /root/testmultithread/mutexlock [Thread debugging using libthread_db enabled] Using host libthread_db library /lib64/libthread_db.so.1. ^C Program received signal SIGINT, Interrupt. 0x00007ffff7bcd4ed in __lll_lock_wait () from /lib64/libpthread.so.0 Missing separate debuginfos, use: debuginfo-install glibc-2.17-260.el7.x86_64 libgcc-4.8.5-36.el7.x86_64 libstdc-4.8.5-36.el7.x86_64 (gdb) bt #0 0x00007ffff7bcd4ed in __lll_lock_wait () from /lib64/libpthread.so.0 #1 0x00007ffff7bc8dcb in _L_lock_883 () from /lib64/libpthread.so.0 #2 0x00007ffff7bc8c98 in pthread_mutex_lock () from /lib64/libpthread.so.0 #3 0x00000000004006f7 in __gthread_mutex_lock (__mutex0x7fffffffe3e0) at /usr/include/c/4.8.2/x86_64-redhat-linux/bits/gthr-default.h:748 #4 0x00000000004007a2 in std::mutex::lock (this0x7fffffffe3e0) at /usr/include/c/4.8.2/mutex:134 #5 0x0000000000400777 in main () at mutexlock.cpp:7 (gdb) f 5 #5 0x0000000000400777 in main () at mutexlock.cpp:7 7 m.lock(); (gdb) l 2 3 int main() 4 { 5 std::mutex m; 6 m.lock(); 7 m.lock(); 8 m.unlock(); 9 10 return 0; 11 } (gdb)我们使用 gdb 运行程序然后使用 bt 命令看到程序确实阻塞在第二个 m.lock() 的地方代码第7行。不管怎样对一个已经调用 lock() 方法再次调用 lock() 方法的做法是错误的我们实际开发中要避免这么做。std::condition_variableC 11 提供了std::condition_variable这个类代表条件变量与 Linux 系统原生的条件变量一样同时提供了等待条件变量满足的wait系列方法wait、wait_for、wait_until 方法发送条件信号使用notify方法notify_one和notify_all方法当然使用std::condition_variable对象时需要绑定一个std::unique_lock或std::lock_guard对象。C 11 中 std::condition_variable 不再需要显式调用方法初始化和销毁。我们将上文中介绍 Linux 条件变量的例子改写成 C 11 版本#include thread #include mutex #include condition_variable #include list #include iostream class Task { public: Task(int taskID) { this-taskID taskID; } void doTask() { std::cout handle a task, taskID: taskID , threadID: std::this_thread::get_id() std::endl; } private: int taskID; }; std::mutex mymutex; std::listTask* tasks; std::condition_variable mycv; void* consumer_thread() { Task* pTask NULL; while (true) { std::unique_lockstd::mutex guard(mymutex); while (tasks.empty()) { //如果获得了互斥锁但是条件不合适的话pthread_cond_wait会释放锁不往下执行。 //当发生变化后条件合适pthread_cond_wait将直接获得锁。 mycv.wait(guard); } pTask tasks.front(); tasks.pop_front(); if (pTask NULL) continue; pTask-doTask(); delete pTask; pTask NULL; } return NULL; } void* producer_thread() { int taskID 0; Task* pTask NULL; while (true) { pTask new Task(taskID); //使用括号减小guard锁的作用范围 { std::lock_guardstd::mutex guard(mymutex); tasks.push_back(pTask); std::cout produce a task, taskID: taskID , threadID: std::this_thread::get_id() std::endl; } //释放信号量通知消费者线程 mycv.notify_one(); taskID ; //休眠1秒 std::this_thread::sleep_for(std::chrono::seconds(1)); } return NULL; } int main() { //创建5个消费者线程 std::thread consumer1(consumer_thread); std::thread consumer2(consumer_thread); std::thread consumer3(consumer_thread); std::thread consumer4(consumer_thread); std::thread consumer5(consumer_thread); //创建一个生产者线程 std::thread producer(producer_thread); producer.join(); consumer1.join(); consumer2.join(); consumer3.join(); consumer4.join(); consumer5.join(); return 0; }编译并执行程序输出结果如下所示[rootlocalhost testmultithread]# g -g -o cpp11cv cpp11cv.cpp -stdc0x -lpthread [rootlocalhost testmultithread]# ./cpp11cv produce a task, taskID: 0, threadID: 140427590100736 handle a task, taskID: 0, threadID: 140427623671552 produce a task, taskID: 1, threadID: 140427590100736 handle a task, taskID: 1, threadID: 140427632064256 produce a task, taskID: 2, threadID: 140427590100736 handle a task, taskID: 2, threadID: 140427615278848 produce a task, taskID: 3, threadID: 140427590100736 handle a task, taskID: 3, threadID: 140427606886144 produce a task, taskID: 4, threadID: 140427590100736 handle a task, taskID: 4, threadID: 140427598493440 produce a task, taskID: 5, threadID: 140427590100736 handle a task, taskID: 5, threadID: 140427623671552 produce a task, taskID: 6, threadID: 140427590100736 handle a task, taskID: 6, threadID: 140427632064256 produce a task, taskID: 7, threadID: 140427590100736 handle a task, taskID: 7, threadID: 140427615278848 produce a task, taskID: 8, threadID: 140427590100736 handle a task, taskID: 8, threadID: 140427606886144 produce a task, taskID: 9, threadID: 140427590100736 handle a task, taskID: 9, threadID: 140427598493440 ...更多输出结果省略...需要注意的是如果在 Linux 平台上std::condition_variable 也存在虚假唤醒这一现象如何避免与上文中介绍 Linux 原生的条件变量方法一样。

相关新闻

全面战争模组制作终极指南:用RPFM轻松打造你的游戏世界

全面战争模组制作终极指南:用RPFM轻松打造你的游戏世界

全面战争模组制作终极指南:用RPFM轻松打造你的游戏世界 【免费下载链接】rpfm Rusted PackFile Manager (RPFM) is a... reimplementation in Rust and Qt6 of PackFile Manager (PFM), one of the best modding tools for Total War Games. 项目地址: https://gi…

2026/7/3 9:04:38阅读更多 →
数据科学团队协作全链路实战指南

数据科学团队协作全链路实战指南

1. 为什么“团队协作是数据科学的核心”不是一句空话,而是每天都在发生的现实“Teamwork is Essential in Data Science”——这句话听起来像职场培训PPT里的标准标语,但如果你真在数据科学一线干过三年以上,就会发现它根本不是口号&#xff…

2026/7/3 9:04:38阅读更多 →
三步实现B站视频下载:解锁大会员4K和充电专属内容的完整解决方案

三步实现B站视频下载:解锁大会员4K和充电专属内容的完整解决方案

三步实现B站视频下载:解锁大会员4K和充电专属内容的完整解决方案 【免费下载链接】bilibili-downloader B站视频下载,支持下载大会员清晰度4K,持续更新中 项目地址: https://gitcode.com/gh_mirrors/bil/bilibili-downloader 你是否经…

2026/7/3 8:59:38阅读更多 →
《Foods》 MDPI 综合食品开源刊,覆盖食品全研究方向,审稿快、创新要求适中,投稿门槛低、版面费适中,适合各类食品应用型成果快速发表

《Foods》 MDPI 综合食品开源刊,覆盖食品全研究方向,审稿快、创新要求适中,投稿门槛低、版面费适中,适合各类食品应用型成果快速发表

01《Foods》 影响因子 5.1 分区 新锐分区2区/JCR Q1 自引率 9.8% 收稿方向 涵盖食品科学、食品技术、食品营养、食品安全等多个方面,包括食品的化学、物理、生物学特性,以及食品加工、保鲜、包装等技术。 2026推荐理由: 投稿量较大…

2026/7/3 10:55:09阅读更多 →
2026跨境电商怎么做能赚稳钱?避开这8个没人说的坑半年多赚20万

2026跨境电商怎么做能赚稳钱?避开这8个没人说的坑半年多赚20万

不少做跨境的从业者最近都在吐槽,欧美市场卷得快喘不过气:流量成本从几年前的几美金涨到现在动辄二三十美金,头部卖家把价格压到几乎没利润,平台规则变来变去,稍不注意就触碰合规红线,熬到最后发现忙活大半…

2026/7/3 10:55:09阅读更多 →
中途可以终止GEO代理合作吗

中途可以终止GEO代理合作吗

做任何代理项目,都要考虑“万一合作不顺利怎么办”。中途终止GEO代理合作是否可以?答案是可以,但具体怎么终止、有什么条件和代价,需要在签合同之前就弄明白,而不是等想退出时才发现被合同锁死了。提前终止合作的三种常…

2026/7/3 10:55:09阅读更多 →
小红书下载终极指南:XHS-Downloader完整教程,轻松保存喜欢的笔记

小红书下载终极指南:XHS-Downloader完整教程,轻松保存喜欢的笔记

小红书下载终极指南:XHS-Downloader完整教程,轻松保存喜欢的笔记 【免费下载链接】XHS-Downloader 小红书(XiaoHongShu、RedNote)链接提取/作品采集工具:提取账号发布、收藏、点赞、专辑作品链接;提取搜索结…

2026/7/3 10:55:09阅读更多 →
2026年跨境电商做独立站必看:5个避开同质化竞争的长尾流量玩法 新手也能快速起量

2026年跨境电商做独立站必看:5个避开同质化竞争的长尾流量玩法 新手也能快速起量

2026年开年以来,跨境电商行业的监管网正在以超乎多数卖家预期的速度收紧,不少深耕行业多年的老卖家都在近期反馈,明明之前正常运营的操作,要么收到平台的违规预警,要么直接被限制了店铺权限,甚至有不少年销…

2026/7/3 10:55:07阅读更多 →
ICM-42605与MK64FN1M0运动追踪系统开发指南

ICM-42605与MK64FN1M0运动追踪系统开发指南

1. 项目背景与核心组件解析在工业自动化和消费电子领域,精确追踪物体在三维空间中的运动状态一直是个关键挑战。ICM-42605作为TDK InvenSense推出的6轴运动传感器,集成了3轴陀螺仪和3轴加速度计,配合NXP的MK64FN1M0VDC12微控制器,…

2026/7/3 10:50:06阅读更多 →
AI Coding 六个月真实ROI账本:产品经理的血泪教训,研发的冷静忠告

AI Coding 六个月真实ROI账本:产品经理的血泪教训,研发的冷静忠告

6个月前的2025年12月,Boris Cherny 公开宣布自己卸载了 IDE。一时间,Vibe Coding 成了全行业最热的话题。6个月后,当我们回过头来拉一份真实账本,发现事情远没有"一句话生成一个App"那么浪漫。本文从产品经理和研发两个…

2026/7/2 12:10:34阅读更多 →
审计来了,数据权限全开——审计走了,怎么确保权限全部关掉?

审计来了,数据权限全开——审计走了,怎么确保权限全部关掉?

引言:审计结束三个月了,审计员的权限还没关某城商行每年按照监管要求开展至少一次数据安全审计。审计期间,内审部门需要抽样检查各类业务数据——交易流水、客户信息、员工操作日志、权限配置记录。这些数据分布在不同系统中,审计…

2026/7/2 12:10:34阅读更多 →
LV3296与PIC18F45K22的UART通信与USB扩展方案

LV3296与PIC18F45K22的UART通信与USB扩展方案

1. LV3296与PIC18F45K22的硬件搭档解析在嵌入式数据采集系统中,LV3296条形码扫描模块与PIC18F45K22微控制器的组合堪称经典搭配。LV3296作为一款工业级条码扫描头,其核心是一颗高性能CMOS图像传感器,配合专用解码芯片,能自动识别包…

2026/7/3 0:03:41阅读更多 →
AI初创生存指南:6个月完成可信度验证闭环

AI初创生存指南:6个月完成可信度验证闭环

1. 这不是“逆袭指南”,而是一份AI初创公司真实生存手记“How To Beat Odds As an AI Startup?”——这个标题乍看像一句热血口号,但在我带过7个从0到1的AI产品团队、亲手踩过融资失败、技术债崩盘、客户POC卡在最后一公里等23类典型坑之后,…

2026/7/3 0:03:41阅读更多 →
多模态+推理链+RAG 2.0+智能体:工业级AI系统落地四支柱

多模态+推理链+RAG 2.0+智能体:工业级AI系统落地四支柱

1. 这不是又一篇“AI趋势速览”,而是一份实操者手记:当多模态、推理链、检索增强与智能体协作真正撞进工程现场“LAI #73”这个编号本身就像一个暗号——它不属于某家大厂的白皮书,也不是学术会议的议程表,而是长期泡在模型训练集…

2026/7/3 0:03:41阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/3 1:12:46阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/3 1:36:36阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/3 2:08:15阅读更多 →