跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

C++ 多线程同步:条件变量 condition_variable 实战

C++ 条件变量用于解决多线程等待通知问题。通过 std::condition_variable 配合 std::mutex 和 std::unique_lock,实现线程间高效协作。核心接口包括 wait、notify_one 和 notify_all。使用带谓词的 wait 可避免虚假唤醒。典型应用为生产者 - 消费者模型,相比轮询机制显著降低 CPU 消耗。多生产者多消费者场景需结合队列容量限制与停止标志管理线程生命周期。

机器人发布于 2026/3/24更新于 2026/10/2178 浏览
C++ 多线程同步:条件变量 condition_variable 实战

C++ 标准库中的条件变量

在多线程编程中,线程需要等待某个条件满足后再执行。例如生产者生产数据后,消费者才能消费;队列不为空时,消费者才能从中取数据。如果仅用互斥锁实现,消费者线程只能不断轮询检查条件,这会造成 CPU 资源的浪费。

引入场景

单纯的轮询会导致 CPU 空转,降低程序运行效率,条件变量就是为解决这类问题而生的。

以下是一个简单的轮询反例,消费者不断检查队列是否有数据:

#include <iostream>
#include <thread>
#include <mutex>
#include <queue>

using namespace std;

queue<int> data_queue;
mutex mtx;

// 生产者
void producer() {
    for (int i = 1; i <= 5; ++i) {
        lock_guard<mutex> lock(mtx);
        data_queue.push(i);
        cout << "生产者生产数据:" << i << endl;
    }
}

// 消费者(轮询方式)
void consumer() {
    while (true) {
        lock_guard<mutex> lock(mtx);
        if (!data_queue.empty()) {
            int data = data_queue.front();
            data_queue.pop();
            cout << "消费者消费数据:" << data << endl;
             (data == ) ;
        }
        
    }
}

{
    ;
    ;
    t_producer.();
    t_consumer.();
     ;
}
if
5
break
// 没有数据时,依然会不断循环检查,浪费 CPU
int main()
thread t_producer(producer)
thread t_consumer(consumer)
join
join
return
0

运行该程序,消费者线程在队列空的时候会一直循环检查,造成不必要的 CPU 开销。

核心接口

C++11 标准库在 <condition_variable> 头文件中提供了 std::condition_variable 类,它需要与 std::mutex 配合使用,实现线程间的高效等待与通知。

  1. wait(unique_lock& lck)

    • 调用该函数的线程会释放持有的互斥锁,并进入阻塞状态。
    • 直到被其他线程的 notify_one() 或 notify_all() 唤醒。
    • 唤醒后,线程会重新获取互斥锁,然后继续执行。
  2. wait(unique_lock& lck, Predicate pred)

    • 带条件的等待,只有当 pred 条件为 false 时才会阻塞。
    • 被唤醒后会先检查条件,条件满足才会继续执行,否则再次阻塞。
    • 该重载可以避免虚假唤醒问题。
  3. notify_one()

    • 唤醒一个正在等待该条件变量的线程。
    • 如果有多个线程等待,随机唤醒其中一个。
  4. notify_all()

    • 唤醒所有正在等待该条件变量的线程。

搭配 unique_lock 的原因

std::condition_variable 的 wait() 函数要求传入 std::unique_lock,而不是 std::lock_guard。 这是因为 wait() 过程中需要临时释放锁,而 std::unique_lock 支持手动解锁和加锁,std::lock_guard 仅支持构造加锁、析构解锁,无法满足需求。

核心结论:条件变量必须与 std::unique_lock 配合使用,才能实现等待时释放锁、唤醒后重新加锁的逻辑。

实战:解决等待 - 通知问题

我们使用 std::condition_variable 改造上述轮询反例,实现高效的生产者 - 消费者模型:

#include <iostream>
#include <thread>
#include <mutex>
#include <queue>
#include <condition_variable>

using namespace std;

queue<int> data_queue;
mutex mtx;
condition_variable cv;
bool is_produced = false;

// 生产者
void producer() {
    for (int i = 1; i <= 5; ++i) {
        lock_guard<mutex> lock(mtx);
        data_queue.push(i);
        cout << "生产者生产数据:" << i << endl;
    }
    is_produced = true;
    cv.notify_all();
}

// 消费者(条件变量方式)
void consumer() {
    unique_lock<mutex> lock(mtx);
    cv.wait(lock, [](){ return !data_queue.empty() || is_produced; });
    while (!data_queue.empty()) {
        int data = data_queue.front();
        data_queue.pop();
        cout << "消费者消费数据:" << data << endl;
    }
}

int main() {
    thread t_producer(producer);
    thread t_consumer(consumer);
    t_producer.join();
    t_consumer.join();
    return 0;
}

运行该程序,消费者线程在没有数据时会进入等待状态,不会浪费 CPU 资源。 生产者生产完成后唤醒消费者,消费者再进行数据消费。

解决虚假唤醒问题

虚假唤醒指的是线程在没有被 notify_one()/notify_all() 唤醒的情况下,也可能从 wait() 中返回。 为了避免这种情况,我们必须使用带条件的 wait() 重载版本,通过判断条件是否满足来决定是否继续执行。

例如,在消费者线程中,我们用 cv.wait(lock, [](){ return !data_queue.empty() || is_produced; }) 替代无参的 wait(),确保只有在队列有数据或生产完成时,线程才会被唤醒并继续执行。

多生产者 - 多消费者模型

实现一个支持多个生产者和多个消费者的模型,使用条件变量保证线程间的同步协作:

#include <iostream>
#include <thread>
#include <mutex>
#include <queue>
#include <condition_variable>
#include <vector>

using namespace std;

const int MAX_QUEUE_SIZE = 5;
queue<int> data_queue;
mutex mtx;
condition_variable cv_producer;
condition_variable cv_consumer;
bool stop_flag = false;

// 生产者函数
void producer_func(int id) {
    for (int i = 1; i <= 3; ++i) {
        unique_lock<mutex> lock(mtx);
        cv_producer.wait(lock, [](){ return data_queue.size() < MAX_QUEUE_SIZE || stop_flag; });
        if (stop_flag) break;
        int data = id * 10 + i;
        data_queue.push(data);
        cout << "生产者" << id << "生产数据:" << data << ",队列大小:" << data_queue.size() << endl;
        cv_consumer.notify_one();
    }
}

// 消费者函数
void consumer_func(int id) {
    while (true) {
        unique_lock<mutex> lock(mtx);
        cv_consumer.wait(lock, [](){ return !data_queue.empty() || stop_flag; });
        if (stop_flag && data_queue.empty()) break;
        int data = data_queue.front();
        data_queue.pop();
        cout << "消费者" << id << "消费数据:" << data << ",队列大小:" << data_queue.size() << endl;
        cv_producer.notify_one();
    }
}

int main() {
    vector<thread> producers;
    vector<thread> consumers;
    for (int i = 1; i <= 2; ++i) {
        producers.emplace_back(producer_func, i);
    }
    for (int i = 1; i <= 3; ++i) {
        consumers.emplace_back(consumer_func, i);
    }
    for (auto& t : producers) {
        t.join();
    }
    stop_flag = true;
    cv_consumer.notify_all();
    for (auto& t : consumers) {
        t.join();
    }
    cout << "所有生产和消费任务完成" << endl;
    return 0;
}

运行效果:

  1. 生产者线程会在队列满时等待,队列有空位时继续生产。
  2. 消费者线程会在队列空时等待,队列有数据时继续消费。
  3. 生产完成后设置停止标志,唤醒所有消费者线程并退出,避免线程阻塞。

协同要点

  1. 条件变量必须搭配互斥锁使用:wait() 函数需要先获取互斥锁,才能保证条件判断的线程安全。
  2. 优先使用带条件的 wait():可以有效避免虚假唤醒,确保线程在正确的条件下被唤醒。
  3. notify_one() 和 notify_all() 的选择:
    • 当只需要唤醒一个等待线程时,使用 notify_one(),效率更高。
    • 当需要唤醒所有等待线程时,使用 notify_all(),比如生产完成后通知所有消费者。

总结

  1. 条件变量用于解决多线程间的等待 - 通知问题,避免了轮询造成的 CPU 资源浪费。
  2. std::condition_variable 必须与 std::unique_lock 配合使用,核心接口是 wait()、notify_one() 和 notify_all()。
  3. 带条件的 wait() 重载版本可以解决虚假唤醒问题,是实际开发中的首选。
  4. 生产者 - 消费者模型是条件变量的典型应用场景,通过合理设计条件可以实现高效的线程协作。

目录

  1. C++ 标准库中的条件变量
  2. 引入场景
  3. 核心接口
  4. 搭配 unique_lock 的原因
  5. 实战:解决等待 - 通知问题
  6. 解决虚假唤醒问题
  7. 多生产者 - 多消费者模型
  8. 协同要点
  9. 总结

更多推荐文章

查看全部
  • Python Flask 多文件项目打包部署:Linux+Docker+Windows 全环境
  • C++ 伸展树与红黑树原理及代码实现
  • 普通笔记本跑 Faster-Whisper:CPU 模式下模型选择指南
  • OpenClaw 上手:环境、模型与飞书集成全流程
  • Seedance 2.0 双分支扩散变换器架构解析与工程实现
  • Java 核心语法与并发编程实战指南
  • Java 包装类与泛型应用详解
  • 深入解剖STL map/multimap:接口使用与核心特性详解
  • F5 刷新时浏览器前端发生了什么?
  • 事件驱动架构:Python 高并发松耦合系统实战
  • Dify MCP Server 插件将工作流发布为第三方可调用服务
  • MCP 插件配置指南:browser-tools-mcp 示例
  • 哈希表实战:四数相加、赎金信与三/四数之和解法
  • C++ 实现 JSON 与 HTTP 协议,从零构建 Web 计算器服务器
  • FAIR plus 机器人全产业链接会:聚焦具身智能与全球协作
  • Fooocus 实战指南:基于 SDXL 的 AI 图像生成入门
  • 利用 AI 工程文档驱动前端开发:TypeDom 儿童英语学习应用实战
  • 解决 npm 安装 OpenClaw 时的 Git 报错与权限问题
  • 基于 CasADi 的无人机非线性模型预测控制(NMPC)设计与仿真
  • RustFS + Docker 生产环境部署指南
  • 相关免费在线工具

    • 加密/解密文本

      使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

    • Gemini 图片去水印

      基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

    • Base64 字符串编码/解码

      将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

    • Base64 文件转换器

      将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

    • Markdown转HTML

      将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

    • HTML转Markdown

      将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online