C++ 并发编程之互斥锁和条件变量的性能比较

发布时间:2026/7/28 18:37:59

C++ 并发编程之互斥锁和条件变量的性能比较
介绍本文以最简单生产者消费者模型通过运行程序观察该进程的cpu使用率来对比使用互斥锁和互斥锁条件变量的性能比较。本例子的生产者消费者模型1个生产者5个消费者。生产者线程往队列里放入数据5个消费者线程从队列取数据取数据前需要判断一下队列中是否有数据这个队列是全局队列是线程间共享的数据所以需要使用互斥锁进行保护。即生产者在往队列里放入数据时其余消费者不能取反之亦然。互斥锁实现的代码#includeiostream// std::cout#includedeque// std::deque#includethread// std::thread#includechrono// std::chrono#includemutex// std::mutex// 全局队列std::dequeintg_deque;// 全局锁std::mutex g_mutex;// 生产者运行标记boolproducer_is_runningtrue;// 生产者线程函数voidProducer(){// 库存个数intcount8;do{// 智能锁初始化后即加锁保护的范围是代码花括号内花括号退出即会自动解锁// 可以手动解锁从而控制互斥锁的细粒度std::unique_lockstd::mutexlocker(g_mutex);// 入队一个数据g_deque.push_front(count);// 提前解锁缩小互斥锁的细粒度只针对共享的队列数据进行同步保护locker.unlock();std::cout生产者 我现在库存有 :countstd::endl;// 放慢生产者生产速度睡1秒std::this_thread::sleep_for(std::chrono::seconds(1));// 库存自减少count--;}while(count0);// 标记生产者打样了producer_is_runningfalse;std::cout生产者 我的库存没有了我要打样了std::endl;}// 消费者线程函数voidConsumer(intid){intdata0;do{std::unique_lockstd::mutexlocker(g_mutex);if(!g_deque.empty()){datag_deque.back();g_deque.pop_back();locker.unlock();std::cout消费者[id] : 我抢到货的编号是 :datastd::endl;}else{locker.unlock();}}while(producer_is_running);std::cout消费者[id] 卖家没有货打样了真可惜下次再来抢std::endl;}intmain(void){std::cout1 producer start ...std::endl;std::threadproducer(Producer);std::cout5 consumer start ...std::endl;std::thread consumer[5];for(inti0;i5;i){consumer[i]std::thread(Consumer,i1);}producer.join();for(inti0;i5;i){consumer[i].join();}std::coutAll threads joined.std::endl;return0;}互斥锁实现运行结果结果输出[rootlincoding condition]# g -stdc0x -pthread -D_GLIBCXX_USE_NANOSLEEP main.cpp -o main[rootlincoding condition]# ./main1 producer start... 5 consumer start... 生产者 我现在库存有 :8 消费者[1]:我抢到货的编号是 :8 消费者[1]:我抢到货的编号是 :7 生产者 我现在库存有 :7 生产者 我现在库存有 :6 消费者[3]:我抢到货的编号是 :6 生产者 我现在库存有 :5 消费者[1]:我抢到货的编号是 :5 生产者 我现在库存有 :4 消费者[2]:我抢到货的编号是 :4 生产者 我现在库存有 :3 消费者[5]:我抢到货的编号是 :3 生产者 我现在库存有 :2 消费者[2]:我抢到货的编号是 :2 生产者 我现在库存有 :1 消费者[1]:我抢到货的编号是 :1 生产者 我的库存没有了我要打样了消费者[5]卖家没有货打样了真可惜下次再来抢 消费者[2]卖家没有货打样了真可惜下次再来抢 消费者[3]卖家没有货打样了真可惜下次再来抢 消费者[4]卖家没有货打样了真可惜下次再来抢 消费者[1]卖家没有货打样了真可惜下次再来抢 All threads joined.可以看到互斥锁其实可以完成这个任务但是却存在着性能问题。Producer是生产者线程在生产者数据过程中会休息1秒所以这个生产过程是很慢的Consumer是消费者线程存在着一个while循环只有判断到生产者不运行了才会退出while循环那么每次在循环体内都是会先加锁判断队列不空然后从列队取出一个数据最后解锁。所以说在生产者休息1秒的时候消费者线程实际上会做很多无用功导致CPU使用率非常高运行的环境是4核cpu[rootlincoding ~]# grep model name /proc/cpuinfo | wc -l4top命令查看cpu使用情况可见使用纯互斥锁cpu的开销是很大的main进程的cpu使用率达到了357.5%CPU系统开销的cpu为54.5%sy用户开销的cpu为18.2%us[rootlincoding ~]# toptop- 19:13:41 up 36 min, 3 users, load average: 0.06, 0.05, 0.01 Tasks: 179 total, 1 running, 178 sleeping, 0 stopped, 0 zombie Cpu(s): 18.2%us, 54.5%sy, 0.0%ni, 27.3%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st Mem: 1004412k total, 313492k used, 690920k free, 41424k buffers Swap: 2031608k total, 0k used, 2031608k free, 79968k cached PID USER PR NI VIRT RES SHR S %CPU %MEM TIME COMMAND 35346 root 20 0 137m 3288 1024 S 357.5 0.3 0:05.92 main 1 root 20 0 19232 1492 1224 S 0.0 0.1 0:02.16 init 2 root 20 0 0 0 0 S 0.0 0.0 0:00.01 kthreadd 3 root RT 0 0 0 0 S 0.0 0.0 0:00.68 migration/0解决的办法之一就是给消费者也加一个小延时当消费者没取到数据时就休息一下500毫秒这样可以减少互斥锁给cpu带来的开销。// 消费者线程函数voidConsumer(intid){intdata0;do{std::unique_lockstd::mutexlocker(g_mutex);if(!g_deque.empty()){datag_deque.back();g_deque.pop_back();locker.unlock();std::cout消费者[id] : 我抢到货的编号是 :datastd::endl;}else{locker.unlock();// 当消费者没取到数据时就休息一下500毫秒std::this_thread::sleep_for(std::chrono::milliseconds(500));}}while(producer_is_running);std::cout消费者[id] 卖家没有货打样了真可惜下次再来抢std::endl;}从运行结果可知cpu使用率大大降低了[rootlincoding ~]# ps aux | grep -v grep |grep mainUSER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 61296 0.0 0.1 141068 1244 pts/1 Sl 19:40 0:00 ./main条件变量互斥锁实现的代码那么问题来了如何确定消费者延时休息多久呢如果生产者生产的非常快消费者却延时了500毫秒也不是很好如果生产者生产的更慢那么消费延时500毫秒也会有无用功占用了CPU这就需要引入条件变量std::condition_variable应用于消费者生产模型中就是生产者生产完一个数据后通过notify_one()唤醒正在wait()消费者线程使得消费者从队列取出一个数据。#includeiostream// std::cout#includedeque// std::deque#includethread// std::thread#includechrono// std::chrono#includemutex// std::mutex#includecondition_variable// std::condition_variable// 全局队列std::dequeintg_deque;// 全局锁std::mutex g_mutex;// 全局条件变量std::condition_variable g_cond;// 生产者运行标记boolproducer_is_runningtrue;// 生产者线程函数voidProducer(){// 库存个数intcount8;do{// 智能锁初始化后即加锁保护的范围是代码花括号内花括号退出即会自动解锁// 可以手动解锁从而控制互斥锁的细粒度std::unique_lockstd::mutexlocker(g_mutex);// 入队一个数据g_deque.push_front(count);// 提前解锁缩小互斥锁的细粒度只针对共享的队列数据进行同步保护locker.unlock();std::cout生产者 我现在库存有 :countstd::endl;// 唤醒一个线程g_cond.notify_one();// 睡1秒std::this_thread::sleep_for(std::chrono::seconds(1));// 库存自减少count--;}while(count0);// 标记生产者打样了producer_is_runningfalse;// 唤醒所有消费线程g_cond.notify_all();std::cout生产者 我的库存没有了我要打样了std::endl;}// 消费者线程函数voidConsumer(intid){// 购买的货品编号intdata0;do{// 智能锁初始化后即加锁保护的范围是代码花括号内花括号退出即会自动解锁// 可以手动解锁从而控制互斥锁的细粒度std::unique_lockstd::mutexlocker(g_mutex);// wait()函数会先调用互斥锁的unlock()函数然后再将自己睡眠在被唤醒后又会继续持有锁保护后面的队列操作// 必须使用unique_lock不能使用lock_guard因为lock_guard没有lock和unlock接口而unique_lock则都提供了g_cond.wait(locker);// 队列不为空if(!g_deque.empty()){// 取出队列里最后一个数据datag_deque.back();// 删除队列里最后一个数据g_deque.pop_back();// 提前解锁缩小互斥锁的细粒度只针对共享的队列数据进行同步保护locker.unlock();std::cout消费者[id] : 我抢到货的编号是 :datastd::endl;}// 队列为空else{locker.unlock();}}while(producer_is_running);std::cout消费者[id] 卖家没有货打样了真可惜下次再来抢std::endl;}intmain(void){std::cout1 producer start ...std::endl;std::threadproducer(Producer);std::cout5 consumer start ...std::endl;std::thread consumer[5];for(inti0;i5;i){consumer[i]std::thread(Consumer,i1);}producer.join();for(inti0;i5;i){consumer[i].join();}std::coutAll threads joined.std::endl;return0;}条件变量互斥锁运行结果[rootlincoding condition]# g -stdc0x -pthread -D_GLIBCXX_USE_NANOSLEEP main.cpp -o main[rootlincoding condition]#[rootlincoding condition]# ./main1 producer start... 5 consumer start... 生产者 我现在库存有 :8 消费者[4]:我抢到货的编号是 :8 生产者 我现在库存有 :7 消费者[2]:我抢到货的编号是 :7 生产者 我现在库存有 :6 消费者[3]:我抢到货的编号是 :6 生产者 我现在库存有 :5 消费者[5]:我抢到货的编号是 :5 生产者 我现在库存有 :4 消费者[1]:我抢到货的编号是 :4 生产者 我现在库存有 :3 消费者[4]:我抢到货的编号是 :3 生产者 我现在库存有 :2 消费者[2]:我抢到货的编号是 :2 生产者 我现在库存有 :1 消费者[3]:我抢到货的编号是 :1 生产者 我的库存没有了我要打样了 消费者[5]卖家没有货打样了真可惜下次再来抢 消费者[1]卖家没有货打样了真可惜下次再来抢 消费者[4]卖家没有货打样了真可惜下次再来抢 消费者[2]卖家没有货打样了真可惜下次再来抢 消费者[3]卖家没有货打样了真可惜下次再来抢 All threads joined.CPU开销非常的小[rootlincoding ~]# ps aux | grep -v grep |grep mainUSER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 73838 0.0 0.1 141068 1256 pts/1 Sl 19:54 0:00 ./main总结在不确定生产者的生产速度是快还是慢的场景里不能只使用互斥锁保护共享的数据这样会对CPU的性能开销非常大可以使用互斥锁条件变量的方式当生产者线程生产了一个数据就唤醒消费者线程进行消费避免一些无用功的性能开销。

相关新闻

Shell - 常用命令

Shell - 常用命令

2026/7/28 18:37:59

l 列出当前行上下各5行,总共10行 q|quit 退出 h 帮助 /for/ 向后搜索字符串for ?for? 向前搜索字符串for x 12 计算算术表达式的值 !! ls -laRt 执行shell命令 n 执行下一条语句 s 4 …

STM32环境监测系统:硬件设计与物联网集成实践

STM32环境监测系统:硬件设计与物联网集成实践

2026/7/28 18:37:59

1. 项目背景与核心功能 这个开源项目基于STM32微控制器构建了一套完整的室内环境监测系统,能够实时采集温湿度、空气质量指数(包括PM2.5浓度)等关键环境参数,并通过机智云平台实现数据可视化与远程监控。在当前智能家居和工业物联…

Qwen3.5大模型微调实战:从环境配置到部署优化

Qwen3.5大模型微调实战:从环境配置到部署优化

2026/7/28 18:37:59

1. Qwen3.5系列模型概述 Qwen3.5是通义千问团队推出的新一代开源大语言模型系列,包含从0.5B到72B不同规模的模型版本。这个系列在语义理解、代码生成和数学推理等方面展现出显著优势,特别在中文场景下的表现尤为突出。与上一代Qwen相比,3.5版…

Codex与Claude Code企业级实战:从环境搭建到项目落地的AI编程指南

Codex与Claude Code企业级实战:从环境搭建到项目落地的AI编程指南

2026/7/28 19:28:01

这次我们来看一个关于 Codex 和 Claude Code 的企业级实战教程资源。对于开发者而言,无论是 OpenAI 的 Codex 还是 Anthropic 的 Claude Code,都代表了当前 AI 辅助编程的顶尖能力。但很多教程要么过于理论,要么缺乏从环境搭建到项目落地的完…

企业数据整合到底卡在哪——老板看不到全盘数据的真相

企业数据整合到底卡在哪——老板看不到全盘数据的真相

2026/7/28 19:28:01

# 企业数据整合到底卡在哪——老板看不到全盘数据的真相## 引言很多企业老板都有过这样的困惑:ERP上了、MES上了、CRM也上了,每个系统都在产生数据,可一到开经营分析会,拿上来的报表口径各不相同,同一笔订单在不同系统…

2026年必看!6款AI论文写作工具深度测评,轻松搞定论文初稿

2026年必看!6款AI论文写作工具深度测评,轻松搞定论文初稿

2026/7/28 19:28:01

学术写作新帮手:AI论文写作工具测评 在准备期刊文章、毕业论文或者职称评审材料时,很多学者都会遇到不少难题。自己动手写论文时,面对海量的文献资料,想找到合适的信息就像找针一样难;再加上论文的格式要求非常严格&a…

一个真·免费、真·开源的远程桌面神器——RustDesk

一个真·免费、真·开源的远程桌面神器——RustDesk

2026/7/28 19:28:01

一个真免费、真开源的远程桌面神器——RustDesk 在远程办公和远程协助日益普及的今天,很多人首先想到的是 TeamViewer、AnyDesk 等商业软件。然而这些工具要么收费昂贵,要么免费版有严格的使用限制。今天我要介绍的主角——RustDesk,是一款完…

Python开发之旅,记那些你知道不知道的坑

Python开发之旅,记那些你知道不知道的坑

2026/7/28 19:28:01

Python开发之旅,记那些你知道不知道的坑一、搭建开发环境二、学习工具运用三、推倒所有重来四、取得初步成效五、阶段成果总结最近有个小程序要开发,是对Oracle数据库的开发。本想用C#开发的,也不知怎么的看到了案头的那本Python的学习书&…

Rooster HD-EV外泌体连续生产培养基:化学成分限定、低颗粒背景与MSC-EV规模化工艺

Rooster HD-EV外泌体连续生产培养基:化学成分限定、低颗粒背景与MSC-EV规模化工艺

2026/7/28 19:18:00

摘要: RoosterBio推出Rooster HD-EV外泌体连续生产培养基,强调高产量、低颗粒背景、工艺简化和2D/3D兼容,用于解决MSC外泌体规模化生产中的关键工艺问题。 关键词:外泌体、外泌体生产培养基、细胞外囊泡、化学成分限定培养基、外…

[具身智能-649]:个人电脑搭建 RTSP 服务完整方案(Windows / Ubuntu 双平台,适配 RDK X5 rtsp2display 调试)

[具身智能-649]:个人电脑搭建 RTSP 服务完整方案(Windows / Ubuntu 双平台,适配 RDK X5 rtsp2display 调试)

2026/7/28 13:30:18

目标:电脑作为RTSP 服务端,循环推送 H264/H265 视频流; RDK X5 通过 rtsp2display 拉流预览,完全不需要在开发板编译 live555。 提供两套成熟方案: ✅ 方案 A:FFmpeg(最简单,优先推…

PDF合并与动态水印的工程化方案:2026国内免费工具实测对比

PDF合并与动态水印的工程化方案:2026国内免费工具实测对比

2026/7/28 16:04:36

一、背景与测试方案 在实际项目交付中,PDF文件合并与版权保护水印的叠加是一个高频但容易被低估的技术需求。典型的处理链路涉及:多源PDF的文件流合并、页面级水印渲染(含透明度混合与图层叠加)、输出文件体积控制。看似简单的操作…

PDF拆分压完图糊了?2026国内免费实测,档案员都在用的组合方案

PDF拆分压完图糊了?2026国内免费实测,档案员都在用的组合方案

2026/7/28 16:04:35

说实话,提到PDF拆分再压缩,我真是被折腾得够呛。 上个月公司年度合同归档,一份300多页的PDF总合同,需要按年份拆分成三个独立文件,再分别压缩到10MB以内方便邮件发送各部门确认。我心想这还不简单?先找个海…

零基础搭建桌面智能体,OpenClaw 2.7.9 分步实操,避开绝大多数部署陷阱

零基础搭建桌面智能体,OpenClaw 2.7.9 分步实操,避开绝大多数部署陷阱

2026/7/28 0:06:55

📌 一、工具核心优势盘点 数据本地存储,安全系数高所有操作日志、文档资料均保存在本机,不会上传至云端,能够有效保护企业文件与个人隐私,规避数据泄露风险。 上手简单,零编程门槛采用全图形化可视化界面&…

计算机毕业设计之基于springboot的购物平台设计与实现

计算机毕业设计之基于springboot的购物平台设计与实现

2026/7/28 0:06:55

由于移动应用技术的持续性的快速发展,现实生活中人们大多数都是通过移动手机、电脑等智能设备来完成生活中的事务。因此,许多的人工传统行业也开始与互联网结合,不再一味的依靠人工手动,努力打造半自动数字化甚至是全自动数字化模…

豆包AI绘图提示词失效真相:NLP模型层token截断机制首次披露,3招绕过字数限制

豆包AI绘图提示词失效真相:NLP模型层token截断机制首次披露,3招绕过字数限制

2026/7/28 0:06:55

更多请点击: https://codechina.net 第一章:豆包AI绘图提示词失效现象全景扫描 近期大量用户反馈,豆包(Doubao)AI绘图功能对常规提示词(Prompt)响应异常:语义明确的指令被忽略、中英…