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

彻底搞懂 C++ 锁、条件变量和生产者-消费者模式

文章目录

  • 彻底搞懂 C++ 锁、条件变量和生产者-消费者模式
    • 一、锁(mutex)到底是什么
    • 二、两种锁的区别
    • 三、条件变量(condition_variable)
      • 不用条件变量——轮询,浪费 CPU
      • 用条件变量——数据没到就睡,到了自动醒
      • wait 到底做了什么
      • notify_one 和 notify_all
      • 为什么 wait 需要一个条件 lambda
    • 四、生产者-消费者模式
      • 最简单版本
      • 为什么两个消费者交替取数据
      • 关键细节
    • 五、我项目里的 BlockingQueue
    • 六、常见面试问题速答
      • Q: lock_guard 和 unique_lock 的区别?
      • Q: 条件变量为什么必须配合 while 或 lambda?
      • Q: 为什么 notify 放在 unlock 后面?
      • Q: 为什么 push 加锁了,pop 的 wait 还能解锁?
    • 七、我记住的关键规则

彻底搞懂 C++ 锁、条件变量和生产者-消费者模式

一、锁(mutex)到底是什么

锁不是代码层面的东西,是约定。你和室友住一间房,门上挂一把锁——谁进去谁锁门,出来再开。另一个人想进去?门口等着。

#include<mutex>std::mutex mtx;// 一把锁void写日志(conststd::string&msg){mtx.lock();// 锁门// 往文件里写日志(这个操作不能同时被两个线程做)mtx.unlock();// 开门}

如果两个线程同时往同一个文件写日志,内容会交叠错乱。加锁后,同一时刻只有一个线程在写。

但手动lock()/unlock()容易出事——写日志写到一半抛异常了,unlock()没执行,锁永远开不了,所有人卡死。所以 C++ 提供了管家:

void写日志(conststd::string&msg){std::lock_guard<std::mutex>lock(mtx);// 构造 = 立刻加锁// 写日志...}// lock 析构 = 自动解锁,就算中间抛异常也一定解锁

lock_guard = 构造加锁,离开花括号自动解锁。永远用它,不要手动调 lock/unlock。


二、两种锁的区别

lock_guardunique_lock
加锁时机构造时自动构造时自动
解锁时机离开{}自动离开{}自动,也可以手动unlock()
能临时解锁再重新加锁吗
能配合条件变量吗
用在哪简单临界区条件变量 / 需要灵活控制的场景
// lock_guard:省心,绝大多数场景用它{std::lock_guard<std::mutex>lock(mtx);// 操作共享数据}// unique_lock:灵活,条件变量必须用它{std::unique_lock<std::mutex>lock(mtx);// 操作共享数据lock.unlock();// 提前解锁,lock_guard 做不到// 可以做其他不涉及共享数据的操作lock.lock();// 重新加锁}

三、条件变量(condition_variable)

锁解决了"同时只能一个人访问"的问题,但解决不了"数据还没到,先睡会儿"的问题。

不用条件变量——轮询,浪费 CPU

while(queue_.empty()){std::this_thread::sleep_for(std::chrono::milliseconds(10));// 每隔 10ms 检查一次,CPU 空转,反应慢}autotask=queue_.front();queue_.pop();

用条件变量——数据没到就睡,到了自动醒

std::unique_lock<std::mutex>lock(mtx);cv.wait(lock,[&]{return!queue_.empty();});// 空了就睡,0% CPUautotask=queue_.front();queue_.pop();

wait 到底做了什么

cv.wait(lock,[&]{return!queue_.empty();});
① 检查条件 !queue_.empty() → ② true → 直接返回,什么都不发生 ③ false → 解锁 lock → 线程挂起(睡觉,0% CPU) ④ 另一个线程调 cv.notify_one() 叫醒我 ⑤ 重新拿锁 → 再检查条件 → true 就取数据

关键:睡觉前解锁,醒了重新拿锁。如果睡觉时还抱着锁不放,别人怎么往队列里放东西?这就是死锁。

notify_one 和 notify_all

cv.notify_one();// 叫醒一个正在 wait 的线程cv.notify_all();// 叫醒所有正在 wait 的线程

为什么 wait 需要一个条件 lambda

因为虚假唤醒——操作系统偶尔会莫名其妙叫醒线程(极少见,但标准允许)。还有一个原因是:你通知了一个线程,但这期间另一个线程可能先醒、先把数据取走了。所以醒过来第一件事必须是检查条件,不满足条件继续睡


四、生产者-消费者模式

这是多线程编程里最常见的模式。一个或多个线程生产数据放到队列里,一个或多个线程从队列里取数据处理。

最简单版本

#include<condition_variable>#include<iostream>#include<mutex>#include<queue>#include<thread>#include<chrono>std::mutex mtx;std::condition_variable cv;std::queue<int>q;boolrunning=true;voidconsumer(intid){while(running||!q.empty()){std::unique_lock<std::mutex>lock(mtx);// 队列空且还在生产 → 睡觉等cv.wait(lock,[]{return!q.empty()||!running;});// running=false 且队列空 → 退出if(!running&&q.empty())break;intval=q.front();q.pop();lock.unlock();// 数据到手就解锁,不拖std::cout<<"消费者"<<id<<" 取走: "<<val<<std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(200));}}voidproducer(){for(inti=1;i<=10;i++){{std::lock_guard<std::mutex>lock(mtx);q.push(i);std::cout<<"生产者 放入: "<<i<<std::endl;}// 离开 {} 自动解锁cv.notify_one();// 叫醒一个消费者std::this_thread::sleep_for(std::chrono::milliseconds(100));}running=false;cv.notify_all();// 叫醒所有人:"下班了,别睡了"}intmain(){std::threadc1(consumer,1);std::threadc2(consumer,2);std::threadp(producer);c1.join();// 等线程结束c2.join();p.join();std::cout<<"全部结束"<<std::endl;return0;}

运行结果大概是:

生产者 放入: 1 消费者 1 取走: 1 生产者 放入: 2 消费者 2 取走: 2 生产者 放入: 3 消费者 1 取走: 3 ... 全部结束

为什么两个消费者交替取数据

因为sleep_for(200ms)让取完数据的消费者暂时不继续抢锁,另一个消费者有时间拿到锁。去掉 sleep 就是纯抢锁,谁抢到谁拿,就不一定交替了。

关键细节

{std::lock_guard<std::mutex>lock(mtx);q.push(i);}// ← 先解锁cv.notify_one();// ← 再通知,不要抱着锁通知

为什么解锁后才通知?如果先通知再解锁——被叫醒的线程立刻尝试拿锁,但锁还没解,又得等。多此一举。解锁后通知,人家醒了直接能拿到锁。


五、我项目里的 BlockingQueue

项目里把这些东西封装成了一个类:

template<typenameT>classBlockingQueue{public:voidpush(T item){{std::lock_guard<std::mutex>lock(mutex_);queue_.push(std::move(item));}cv_.notify_one();// 解锁后通知}Tpop(){std::unique_lock<std::mutex>lock(mutex_);// 用 unique_lockcv_.wait(lock,[&]{return!queue_.empty();});// 空了就睡T item=std::move(queue_.front());queue_.pop();returnitem;}private:std::mutex mutex_;std::condition_variable cv_;std::queue<T>queue_;};

用法简单:

// 生产者(odud 的 IPC 接收线程):tx_queue_.push(OduSerialTask{...});// 消费者(odud 的串口发送线程):OduSerialTask task=tx_queue_.pop();// 空了自动睡,不占 CPU

六、常见面试问题速答

Q: lock_guard 和 unique_lock 的区别?

A: lock_guard 只有构造加锁 + 析构解锁,不能手动控制。unique_lock 可以临时 unlock 再 lock,条件变量的 wait 必须用 unique_lock。大部分场景用 lock_guard 就够了。

Q: 条件变量为什么必须配合 while 或 lambda?

A: 因为虚假唤醒。线程可能被操作系统莫名其妙叫醒,也可能被 notify 但数据被另一个线程抢了。醒过来必须重新检查条件,不满足继续睡。

Q: 为什么 notify 放在 unlock 后面?

A: 先 unlock 再 notify——被叫醒的线程直接能拿到锁,不用再等。反过来先 notify 再 unlock,被叫醒的线程发现锁还没解,又要等,浪费一次上下文切换。

Q: 为什么 push 加锁了,pop 的 wait 还能解锁?

A: wait 内部做的事是:临时解锁 → 挂起 → 被叫醒 → 重新加锁。push 在 wait 解锁期间可以拿到锁、放数据。这正是条件变量的核心机制。


七、我记住的关键规则

  1. 任何共享数据被多线程读写 → 加锁。lock_guard,别手写 lock/unlock。
  2. 加锁后不做耗时操作。拿数据就解锁,别抱着锁做 IO。
  3. 生产者-消费者 = 队列 + 锁 + 条件变量。生产者 push + notify,消费者 pop + wait。
  4. wait 必须传条件判断。要么 lambda,要么 while 套 wait。防止虚假唤醒。
  5. 先解锁再 notify。不抱着锁通知别人。
http://www.jsqmd.com/news/1151770/

相关文章:

  • 如何识别 AI 生成的图像?这款免费的工具给你答案!
  • 模型精度 vs 推理成本:90 分模型和 85 分模型,账单差三倍
  • 破解AI影视声画割裂难题!集之互动智能音画协同技术,升级沉浸式观影体验
  • 你好大家好
  • TikTok Shop商品批量翻译与图片处理解析
  • 算清账再花对钱,如何通过数字化赋能实现高效且可持续的人效提升?
  • 终极指南:如何用League Akari免费开源工具快速提升英雄联盟游戏表现
  • 2026年成都全域优化搜索服务商深度评测:技术沉淀与性价比的完美平衡
  • 制造业ERP系统推荐榜单有哪些?5款主流厂商选型参考
  • 这个智能中控用了半年,体验到底怎么样?
  • 影刀RPA Excel数据透视表:分组统计基础
  • Velox实战教程:5个步骤构建高性能数据处理系统
  • Go 微服务优雅上下线:PreStop Hook 和 Readiness Probe 的协作
  • Marvell QLogic适配器在ARM64平台上的终极性能测试指南:openeuler/qla2xxx驱动深度解析
  • 如何快速掌握AcFun视频下载:免费开源工具AcFunDown终极指南
  • 带 AI 智能助手的研发管理工具测评:9 款主流平台怎么选?
  • 算法优化中的“空间换时间”与“时间换空间”再思考7
  • 3个会议录音转写工具横评:分角色识别、AI总结谁更强?职场新人10分钟搞定会议纪要的秘诀
  • Windows 环境使用 Git Bash 最佳实践
  • AI编程实战:Codex与Spec Coding重塑全栈开发流程
  • Vivado IBERT 7系列GTX误码率测试:4种环回模式对比与眼图扫描实战
  • Claude Code × MCP企业级部署手册(含SOC2合规配置模板、审计日志字段映射表、密钥轮换SOP)
  • Okbiye 毕业论文专属创作模块,一站式化解毕业生撰文全流程阻碍
  • Python基础知识(一)
  • gpt-5.5 一直报 429 但 gpt-5.4-pro 同样请求没问题怎么办?两种并发限速规则拆解 + 退避策略实战
  • 论文精读:土地利用效率对生态韧性的非线性与空间异质效应——基于太行山地区GeoXAI空间可解释机器学习框架
  • 豆包AI品牌提及监测中的问题库、采样与统计方法
  • 终极指南:如何使用RealBasicVSR免费实现4倍视频画质提升
  • 中小企业官网适合用AI建站吗?中小企业使用AI建站有什么好处?一文说清!
  • 房车设备里的串口屏,大家都是怎么选的?求经验分享