0

0

如何在C++中使用条件变量_C++多线程同步之条件变量

裘德小鎮的故事

裘德小鎮的故事

发布时间:2025-09-24 09:35:02

|

608人浏览过

|

来源于php中文网

原创

std::condition_variable与std::mutex配合解决线程等待与通知问题,避免忙等待,在生产者-消费者模型中通过wait和notify实现高效协作,防止虚假唤醒需使用谓词,避免丢失唤醒应先加锁再检查条件。

如何在c++中使用条件变量_c++多线程同步之条件变量

在C++多线程编程里,当我们遇到一个线程需要等待某个条件满足才能继续执行的情况,std::condition_variable就是那个常常被我们请出来的“协调员”。它不是一个独立的锁,而是一个与std::mutex协同工作的工具,主要职责是让线程在特定条件下进入休眠状态,直到另一个线程发出信号唤醒它。简单来说,它解决了“等待”这个难题,避免了无谓的忙等待,让线程更高效地利用CPU资源。

解决方案

要在C++中有效地使用条件变量,核心思想是将其与互斥锁(std::mutex)结合起来,共同管理共享数据的访问和线程间的通知。最经典的场景莫过于生产者-消费者模型。

#include 
#include 
#include 
#include 
#include 
#include  // For std::chrono::milliseconds

std::mutex mtx; // 互斥锁,保护共享数据
std::condition_variable cv; // 条件变量,用于线程间通信
std::queue data_queue; // 共享数据队列
const int MAX_QUEUE_SIZE = 5; // 队列最大容量

// 生产者线程
void producer() {
    for (int i = 0; i < 10; ++i) {
        std::unique_lock lock(mtx); // 锁定互斥量

        // 等待队列不满。如果队列已满,生产者线程在此处等待
        // wait()会自动释放锁并阻塞,被唤醒后会重新获取锁
        cv.wait(lock, [&]{ return data_queue.size() < MAX_QUEUE_SIZE; });

        data_queue.push(i); // 生产数据
        std::cout << "Producer produced: " << i << ". Queue size: " << data_queue.size() << std::endl;

        lock.unlock(); // 提前释放锁,让消费者有机会竞争
        cv.notify_one(); // 通知一个等待中的消费者线程

        std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟生产耗时
    }
    std::cout << "Producer finished." << std::endl;
}

// 消费者线程
void consumer() {
    for (int i = 0; i < 10; ++i) {
        std::unique_lock lock(mtx); // 锁定互斥量

        // 等待队列不空。如果队列为空,消费者线程在此处等待
        cv.wait(lock, [&]{ return !data_queue.empty(); });

        int data = data_queue.front(); // 消费数据
        data_queue.pop();
        std::cout << "Consumer consumed: " << data << ". Queue size: " << data_queue.size() << std::endl;

        lock.unlock(); // 提前释放锁,让生产者有机会竞争
        cv.notify_one(); // 通知一个等待中的生产者线程

        std::this_thread::sleep_for(std::chrono::milliseconds(150)); // 模拟消费耗时
    }
    std::cout << "Consumer finished." << std::endl;
}

// int main() {
//     std::thread prod_thread(producer);
//     std::thread cons_thread(consumer);

//     prod_thread.join();
//     cons_thread.join();

//     std::cout << "All threads finished." << std::endl;
//     return 0;
// }

这个例子里,std::unique_lock确保了对data_queue的独占访问。cv.wait()是关键:它会在条件不满足时释放锁并让当前线程休眠,直到被notify_one()notify_all()唤醒,并重新获取锁。传入的lambda表达式(谓词)是防止虚假唤醒和“丢失的唤醒”的关键。

条件变量究竟解决了哪些痛点?它和互斥量有什么不同?

说实话,刚接触多线程的时候,我常常会把互斥量和条件变量的概念搞混,或者觉得互斥量是不是就够用了。但深入下去,你会发现它们俩是完全不同的角色,却又密不可分。互斥量(std::mutex)的核心职责是保护共享资源,确保在任何时刻只有一个线程能访问它,避免数据竞争。它就像一道门,一次只能进出一个人。

立即学习C++免费学习笔记(深入)”;

但光有门还不够。设想一个场景:一个线程A需要处理某个数据,但这个数据还没准备好。如果线程A只是傻傻地用一个循环去不断检查数据是否准备好(也就是所谓的“忙等待”),那它就会白白消耗CPU资源,效率极低。这就是互斥量解决不了的痛点——线程间的协作与等待

条件变量(std::condition_variable)就是来解决这个问题的。它提供了一种机制,让一个线程可以在某个条件不满足时主动挂起(休眠),释放互斥锁,等待其他线程通知它条件已经满足。一旦条件满足,被通知的线程就会被唤醒,重新尝试获取互斥锁,然后继续执行。这就像是门旁边的一个呼叫器:数据没准备好,你就按一下呼叫器,然后去休息,等数据准备好了,有人会按呼叫器通知你。

Amazon Nova
Amazon Nova

亚马逊云科技(AWS)推出的一系列生成式AI基础模型

下载

所以,它们的不同点很明显:

  • 互斥量:解决资源访问冲突问题,保证原子性。
  • 条件变量:解决线程等待与通知问题,实现线程间的协作。

它们之所以要配合使用,是因为条件变量在等待时需要释放互斥锁,这样其他线程才能进入临界区改变条件。被唤醒的线程也需要重新获取互斥锁,才能安全地检查条件并访问共享数据。没有互斥锁的保护,条件变量的等待和通知机制就毫无意义,甚至会引入新的数据竞争问题。我个人觉得,理解它们的这种“共生”关系,是掌握C++多线程同步的关键一步。

使用std::condition_variable时有哪些常见的陷阱和最佳实践?

在使用std::condition_variable时,虽然它功能强大,但确实有些地方稍不注意就可能踩坑。我总结了几点,也算是自己摸索过程中吃过亏的地方。

  1. 虚假唤醒(Spurious Wakeups):这是最常见的陷阱之一。条件变量的wait()方法有时可能会在没有notify_one()notify_all()调用时被唤醒。这听起来有点反直觉,但确实会发生,而且是标准允许的行为。

    • 最佳实践始终使用谓词(predicate)cv.wait(lock, []{ return condition; }); 这种形式是强烈推荐的。wait方法会在被唤醒后,自动重新检查谓词。如果谓词仍然为false,它会再次释放锁并进入等待状态。这确保了线程只在真正需要时才继续执行,有效规避了虚假唤醒带来的问题。
  2. “丢失的唤醒”(Lost Wakeups):如果一个notify_one()notify_all()调用发生在wait()方法被调用之前,那么这个唤醒信号就可能被“丢失”了,导致本应被唤醒的线程永远等待下去。这通常发生在条件已经满足,但等待线程还没来得及进入wait状态的时候。

    • 最佳实践
      • 在改变条件后立即调用notify_one()notify_all()。虽然理论上可以在解锁互斥量后调用notify,但在互斥量保护

相关专题

更多
lambda表达式
lambda表达式

Lambda表达式是一种匿名函数的简洁表示方式,它可以在需要函数作为参数的地方使用,并提供了一种更简洁、更灵活的编码方式,其语法为“lambda 参数列表: 表达式”,参数列表是函数的参数,可以包含一个或多个参数,用逗号分隔,表达式是函数的执行体,用于定义函数的具体操作。本专题为大家提供lambda表达式相关的文章、下载、课程内容,供大家免费下载体验。

202

2023.09.15

python lambda函数
python lambda函数

本专题整合了python lambda函数用法详解,阅读专题下面的文章了解更多详细内容。

189

2025.11.08

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

473

2023.08.10

Python 多线程与异步编程实战
Python 多线程与异步编程实战

本专题系统讲解 Python 多线程与异步编程的核心概念与实战技巧,包括 threading 模块基础、线程同步机制、GIL 原理、asyncio 异步任务管理、协程与事件循环、任务调度与异常处理。通过实战示例,帮助学习者掌握 如何构建高性能、多任务并发的 Python 应用。

131

2025.12.24

php源码安装教程大全
php源码安装教程大全

本专题整合了php源码安装教程,阅读专题下面的文章了解更多详细内容。

65

2025.12.31

php网站源码教程大全
php网站源码教程大全

本专题整合了php网站源码相关教程,阅读专题下面的文章了解更多详细内容。

45

2025.12.31

视频文件格式
视频文件格式

本专题整合了视频文件格式相关内容,阅读专题下面的文章了解更多详细内容。

40

2025.12.31

不受国内限制的浏览器大全
不受国内限制的浏览器大全

想找真正自由、无限制的上网体验?本合集精选2025年最开放、隐私强、访问无阻的浏览器App,涵盖Tor、Brave、Via、X浏览器、Mullvad等高自由度工具。支持自定义搜索引擎、广告拦截、隐身模式及全球网站无障碍访问,部分更具备防追踪、去谷歌化、双内核切换等高级功能。无论日常浏览、隐私保护还是突破地域限制,总有一款适合你!

41

2025.12.31

出现404解决方法大全
出现404解决方法大全

本专题整合了404错误解决方法大全,阅读专题下面的文章了解更多详细内容。

232

2025.12.31

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 3.2万人学习

Pandas 教程
Pandas 教程

共15课时 | 0.9万人学习

ASP 教程
ASP 教程

共34课时 | 3.1万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号