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

【C++ 线程同步终极篇】condition_variable 条件变量 /wait/wait_for /notify 实战精讲

前言

在 C++ 多线程开发中,互斥锁 mutex只能解决 “数据竞争” 问题,但无法解决线程间协调与等待

例如:

  • 线程 A 必须等线程 B 做完某件事才能继续

  • 多个线程按顺序打印(A→B→C→A→B→C)

  • 生产者生产完,消费者才能消费

这就必须用到std::condition_variable条件变量

从原理到手写实现,精讲:

  1. wait/wait_for/wait_until用法

  2. 虚假唤醒(必须用循环判断!)

  3. 惊群效应

  4. 带谓词的 wait(自动防虚假唤醒)

  5. 超时等待

  6. 三道经典题:

    三线程轮流打印 0~100

    三线程轮流打印 ABC (10 组)

    条件变量阻塞与 notify 实战


一、条件变量核心原理

condition_variable必须配合unique_lock<mutex>使用。

它的核心能力:

  1. 让线程阻塞等待(释放锁 → 不占 CPU)

  2. 被其他线程唤醒(重新抢锁 → 继续执行)

最关键四步(背会!)

当调用cv.wait(lock)时,内部会自动做 4 件事:

  1. unlock解锁(让其他线程能进临界区)

  2. 把当前线程加入等待队列

  3. 等待被唤醒

  4. 被唤醒后重新lock抢锁


二、四种 wait 用法

2.1 基础 wait + while 防虚假唤醒

// 必须用 while!!! 防止虚假唤醒 while (i == 0) { cv.wait(locker); cerr << "被唤醒... " << endl; }

为什么必须 while?

因为操作系统可能会无缘无故唤醒你(虚假唤醒)。

唤醒后条件不满足,必须继续等。


2.2 带谓词的 wait(推荐写法)

cv.wait(locker, [=]() -> bool { return i == 1; // 条件满足才停止等待 });

等价于:

while(!条件) wait(locker);

系统自动帮你做 while 循环,自动防虚假唤醒。


2.3 wait_for 超时等待

auto status = cv.wait_for(locker, 1000ms); if (status == cv_status::timeout) { // 超时了 } else { // 被唤醒 }

2.4 wait_for + 谓词(最常用、最安全)

cv.wait_for(locker, 1000ms, [ch]() -> bool { return i == 1; });
  • 每 1s 自动醒一次

  • 条件满足就退出

  • 防超时 + 防虚假唤醒


三、主程序示例(可直接运行)

#include<iostream> #include<string> #include<memory> // 智能指针 #include<atomic> // 原子操作 #include<thread> // 线程 #include<chrono> // 时间库 #include<mutex> // 互斥锁 #include<condition_variable> // 条件变量 using namespace std; std::condition_variable cv; // 条件变量:用于线程等待与唤醒 std::mutex mtx; // 互斥锁:保护共享数据 int i = 0; // 共享条件:控制线程是否退出等待 int num = 0; // 统计唤醒次数 // 等待线程函数:被阻塞,直到 i == 1 void Waits(char ch) { // 1. 创建 unique_lock 并自动加锁(必须用 unique_lock,因为 wait 内部要解锁) std::unique_lock<std::mutex> locker(mtx); std::cerr << "waiting... funa : " << ch << endl; // =================== (4) wait_for + 谓词【最安全、最实用、企业级写法】================== // 每 1s 醒一次 + 判断条件 + 防虚假唤醒 + 防死等 bool tag = false; do { // 等待 1000ms,若条件 i==1 满足则退出等待 tag = cv.wait_for(locker, std::chrono::milliseconds(1000), [ch]()->bool { cerr << "[]()->bool{}" << ++num << " " << ch << endl; return i == 1; // 条件满足返回 true }); cerr << " tag: " << tag << endl; } while (!tag); // 等待结束,线程继续执行 std::cerr << ".. finished waiting .i = " << i << "tag: " << tag << endl; } // 唤醒线程函数:负责多次唤醒,最后设置 i=1 void Signals() { // 先休眠 2 秒,让等待线程先进入 wait std::this_thread::sleep_for(std::chrono::seconds(2)); { // 加锁只是为了让打印有序,不影响条件变量 std::lock_guard<std::mutex> locker(mtx); cerr << "Notifying ... \n"; } cv.notify_all(); // 唤醒所有等待线程(会产生惊群效应) // 再次休眠 2 秒 std::this_thread::sleep_for(std::chrono::seconds(2)); { std::lock_guard<std::mutex> locker(mtx); cerr << "Notifying ... \n"; } cv.notify_all(); // 再次唤醒所有 // 最后设置条件 i=1,再唤醒 std::this_thread::sleep_for(std::chrono::seconds(2)); { std::lock_guard<std::mutex> locker(mtx); i = 1; // 满足等待条件 cerr << "Notifying again ... \n"; } cv.notify_all(); // 最终唤醒所有线程,退出等待 } int main() { // 创建 3 个等待线程 + 1 个唤醒线程 std::thread tha(Waits, 'A'); std::thread thb(Waits, 'B'); std::thread thc(Waits, 'C'); std::thread thd(Signals); tha.join(); thb.join(); thc.join(); thd.join(); return 0; }

四、经典题

4.1:三线程轮流打印 0~100

线程 A:打印 0 3 6 9 ...

线程B:打印 1 4 7 10 ...

线程C:打印 2 5 8 11 ...

std::condition_variable cv; std::mutex mtx; int i = 0; const int n = 100; void funa() { std::unique_lock<std::mutex> locker(mtx); while (i <= n) { // 条件不满足就一直等待(防虚假唤醒) while (i <= n && i % 3 != 0) { // 每 10ms 醒来检查一次 cv.wait_for(locker, std::chrono::milliseconds(10)); } if (i <= n) { printf("funa : %d \n", i); } i += 1; cv.notify_one(); // 唤醒一个线程 } } void funb() { std::unique_lock<std::mutex> locker(mtx); while (i <= n) { while (i <= n && i % 3 != 1) { cv.wait_for(locker, std::chrono::milliseconds(10)); } if (i <= n) { printf("funb : %d \n", i); } i += 1; cv.notify_one(); } } void func() { std::unique_lock<std::mutex> locker(mtx); while (i <= n) { while (i <= n && i % 3 != 2) { cv.wait_for(locker, std::chrono::milliseconds(10)); } if (i <= n) { printf("func : %d \n", i); } i += 1; cv.notify_one(); } } int main() { std::thread tha(funa), thb(funb), thc(func); tha.join(); thb.join(); thc.join(); return 0; }

4.2 三线程轮流打印 ABC (10 组)

std::condition_variable cv; std::mutex mtx; int i = 0; const int n = 10; char ch = 'A'; void funa() { std::unique_lock<std::mutex> locker(mtx); for (int j = 0; j < n; ++j) { // 必须是 A 才能打印,否则等待 while (ch != 'A') { cv.wait(locker); } printf("funa %c \n", ch); ch = 'B'; // 切换为 B cv.notify_all();// 唤醒所有线程 } } void funb() { std::unique_lock<std::mutex> locker(mtx); for (int j = 0; j < n; ++j) { while (ch != 'B') { cv.wait(locker); } printf("funb %c \n", ch); ch = 'C'; cv.notify_all(); } } void func() { std::unique_lock<std::mutex> locker(mtx); for (int j = 0; j < n; ++j) { while (ch != 'C') { cv.wait(locker); } printf("func %c \n", ch); ch = 'A'; cv.notify_all(); } } int main() { std::thread tha(funa), thb(funb), thc(func); tha.join(); thb.join(); thc.join(); return 0; }

五、高频问题

1. 条件变量为什么必须用 unique_lock?

因为wait内部要自动 unlock + 重新 locklock_guard不支持手动解锁。

2. 什么是虚假唤醒?

操作系统无缘无故唤醒线程,条件并未满足。解决:必须用 while 或 带谓词的 wait。

3. 什么是惊群效应?

notify_all()唤醒所有等待线程,只有一个能运行,其他继续等待,浪费资源。

4. wait /wait_for 的区别?

  • wait:一直等,直到被唤醒

  • wait_for:最多等一段时间,超时自动醒

5. 条件变量的执行四步?

unlock

加入等待队列

等待唤醒

重新 lock


五、总结

C++ 条件变量condition_variable线程同步的核心工具,用于线程间等待与唤醒。

核心要点:

  1. 必须配合unique_lock<mutex>

  2. wait会自动解锁 + 阻塞 + 唤醒后重新加锁

  3. 必须防虚假唤醒(while / 谓词)

  4. wait_for支持超时,更安全

  5. notify_one唤醒一个,notify_all唤醒全部(惊群)


http://www.jsqmd.com/news/549681/

相关文章:

  • 避坑指南:Avalonia中使用ReactiveUI绑定事件的3种正确姿势
  • 2026年防排烟岩棉厂家推荐:廊坊德腾保温材料有限公司,岩棉保温板/岩棉毡/暖气保温管厂精选 - 品牌推荐官
  • OpenArk内核驱动加载故障深度解决方案:从诊断到优化的完整指南
  • 如何深度定制Insyde BIOS隐藏选项:完整的技术指南
  • 个人电脑应用记录
  • 2026年哈尔滨汽车维修公司选购指南,严东养车口碑好服务佳 - 工业推荐榜
  • 2026专业的企业直播陪跑机构排名,河南慧抖新媒体优势探讨 - myqiye
  • 探索话费卡回收方法:避免常见误区,提高回收收益! - 团团收购物卡回收
  • 文本驱动的协作可视化:用Mermaid实现技术文档自动化
  • K8s配置管理实战:如何优雅地通过ConfigMap挂载应用配置文件
  • 如何高效使用XUnity.AutoTranslator:Unity游戏智能翻译的完整指南
  • InternGPT完全入门指南:从零开始掌握5大基础操作
  • 从收音机杂音到自动驾驶安全:聊聊CISPR25标准背后的那些事儿
  • Wiki.js日志系统终极指南:从记录到安全监控的全面解析
  • Pixel Dimension Fissioner 与Claude协同创作:利用大语言模型构思像素画叙事
  • 2020 年 12 月青少年软编等考 C 语言三级真题解析
  • 2026年哈尔滨性价比高的专业隐形车衣公司,费用多少 - 工业设备
  • 自动化素材中枢:实现云端文件与外部群消息的异步同步方案
  • AltTab:终极macOS窗口管理神器,让Windows用户无缝切换
  • 探讨2026年福建得力机电实力怎么样,对比同行优势凸显 - mypinpai
  • 用HTML Canvas和JavaScript打造可交互的网页烟花秀(附完整源码)
  • GD32F4xx GPIO实战:用推挽输出和上拉输入驱动外部按键与LED(附状态机思路)
  • AprilGrid标定板坐标系统解析与视觉定位实践
  • csvlens作为库使用教程:在Rust项目中集成CSV查看功能
  • 2026年重庆变频控制柜公司推荐:重庆皇宏科技,APP/物联网/变频控制柜等全系产品助力工业4.0 - 品牌推荐官
  • 告别海量标注!用Wav2Vec 2.0在10分钟语音数据上跑出可用ASR模型
  • 2026年电源设备厂家推荐:深圳市杰创立仪器,直流/精密变频/线性电源等全系产品供应 - 品牌推荐官
  • SpringBoot+MyBatis事务控制实战:从默认行为到精细化手动管理
  • 医学图像分割新突破:手把手教你用VM-UNet实现皮肤病变精准识别
  • 分析合肥高层装饰专业吗,它在合肥肥东县口碑和性价比怎么样? - 工业设备