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

C++中的条件变量(condition_variable)详解:小白版

生成带比例的小猫图片 (1).png

在编程中,我们经常需要处理多个任务,这些任务可能需要同时运行,也可能需要按照一定的顺序运行。这就涉及到了线程的概念。线程就像是一个小程序,它可以在程序中独立运行,而且可以和其他线程并行执行。

但是,有时候我们需要控制线程的执行顺序,比如有两个线程A和B,我们希望A执行完后,B才能开始执行。这就需要一种机制来同步线程的执行,这就是条件变量(std::condition_variable)的作用。

1. 什么是条件变量?

条件变量是一种特殊的变量,它可以让一个线程在某个条件成立之前等待,当条件成立时,这个线程就可以继续执行。条件变量通常和另一种叫做互斥锁(std::mutex)的东西一起使用,互斥锁可以保证在同一时间只有一个线程能访问某个资源。

2. 条件变量是如何工作的?

假设我们有两个线程A和B,我们希望A执行完后,B才能开始执行。我们可以这样做:

  1. 创建一个条件变量和一个互斥锁。
  2. 在A线程中,我们先锁定互斥锁,然后执行A线程的任务,任务完成后,我们解锁互斥锁,并通知条件变量。
  3. 在B线程中,我们也先锁定互斥锁,然后让B线程等待条件变量。当A线程通知条件变量后,B线程就会被唤醒,然后执行B线程的任务。

3. 条件变量的主要方法

条件变量有三个主要的方法:

  • wait:这个方法会让当前线程等待,直到条件变量被通知。
  • notify_one:这个方法会唤醒一个等待的线程。
  • notify_all:这个方法会唤醒所有等待的线程。

4. 条件变量的使用实例

让我们通过一个简单的实例来理解条件变量的使用。假设我们有两个线程,一个生产者线程和一个消费者线程。生产者线程负责生成数据,消费者线程负责处理数据。我们希望当生产者线程生成了数据后,消费者线程才开始处理数据。

首先,我们需要包含必要的头文件,并定义一些全局变量:

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

std::mutex mtx;
std::condition_variable cv;
bool ready = false;  // 用于表示数据是否已经生成

然后,我们定义生产者线程的函数:

void producer() {
    std::this_thread::sleep_for(std::chrono::seconds(2));  // 模拟数据生成过程
    std::lock_guard<std::mutex> lock(mtx);
    ready = true;  // 数据已经生成
    cv.notify_one();  // 通知消费者线程
}

接着,我们定义消费者线程的函数:

void consumer() {
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, []{return ready;});  // 等待数据生成
    std::cout << "Consumer thread is processing data...\n";
    // 模拟数据处理过程
    std::this_thread::sleep_for(std::chrono::seconds(2));
    std::cout << "Data processed.\n";
}

最后,我们在主函数中启动这两个线程:

int main() {
    std::thread t1(producer);
    std::thread t2(consumer);
    t1.join();
    t2.join();
    return 0;
}

运行这个程序,你会看到消费者线程会等待生产者线程生成数据,当数据生成后,消费者线程才开始处理数据。

5. 生产者消费者问题的扩展

在上述的生产者消费者问题中,我们只有一个生产者和一个消费者。但在实际的应用中,我们可能会有多个生产者和消费者。此时,我们需要使用一个队列来存储数据,生产者将数据放入队列,消费者从队列中取出数据。

首先,我们需要定义一个队列和一些全局变量:

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

std::mutex mtx;
std::condition_variable cv;
std::queue<int> data_queue;  // 数据队列
bool finished = false;  // 用于表示生产者是否已经完成数据生成

然后,我们定义生产者线程的函数:

void producer(int id) {
    for (int i = 0; i < 5; ++i) {
        std::this_thread::sleep_for(std::chrono::seconds(1));  // 模拟数据生成过程
        std::lock_guard<std::mutex> lock(mtx);
        data_queue.push(i);
        std::cout << "Producer " << id << " produced data " << i << std::endl;
        cv.notify_one();  // 通知消费者线程
    }
}

接着,我们定义消费者线程的函数:

void consumer(int id) {
    while (true) {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, []{return !data_queue.empty() || finished;});  // 等待数据生成
        while (!data_queue.empty()) {
            int data = data_queue.front();
            data_queue.pop();
            std::cout << "Consumer " << id << " consumed data " << data << std::endl;
            // 模拟数据处理过程
            std::this_thread::sleep_for(std::chrono::seconds(1));
        }
        if (data_queue.empty() && finished) {
            break;
        }
    }
}

最后,我们在主函数中启动这些线程:

int main() {
    std::thread producers[2];
    std::thread consumers[2];
    for (int i = 0; i < 2; ++i) {
        producers[i] = std::thread(producer, i + 1);
        consumers[i] = std::thread(consumer, i + 1);
    }
    for (auto &p : producers) {
        p.join();
    }
    finished = true;
    cv.notify_all();
    for (auto &c : consumers) {
        c.join();
    }
    return 0;
}

在这个例子中,我们有两个生产者和两个消费者。生产者将数据放入队列,消费者从队列中取出数据。当所有的生产者都完成数据生成后,我们设置finishedtrue,并通知所有的消费者线程。

这就是如何使用条件变量来解决多生产者和多消费者的问题。通过使用条件变量,我们可以实现更复杂的线程同步需求。


http://www.kler.cn/a/502437.html

相关文章:

  • CNN张量输入形状和特征图
  • 详解 Docker 启动 Windows 容器第二篇:技术原理与未来发展方向
  • 理解AJAX与Axios:异步编程的世界
  • python 寻找数据拐点
  • 学习笔记080——如何备份服务器中Docker创建的MySQL数据库数据?
  • 精品PPT | AI+智能中台企业架构设计_重新定义制造
  • 移动应用安全:保护用户隐私与数据的关键解决方案
  • 安全运营体系建设
  • 字符串 (算法十一)
  • 【8】深入理解 Go 语言中的协程-从基础到高级应用
  • django基于Python对西安市旅游景点的分析与研究
  • 探秘 JMeter (Interleave Controller)交错控制器:解锁性能测试的隐藏密码
  • Go语言之路————func
  • Golang笔记——语言基础知识
  • PyTorch 张量的分块处理介绍
  • 鸿蒙UI开发——带农历的日期滑动选择弹窗
  • 74 mysql having 的实现
  • 数据结构与算法之链表: LeetCode 234. 回文链表 (Ts版)
  • sql server 对 nvarchar 类型的列进行 SUM() 运算
  • Spring Boot 动态表操作服务实现
  • OS1.【Linux】大致介绍和环境搭建
  • Redis高危漏洞-GHSA-whxg-wx83-85p5:用户可能会使用特制的 Lua 脚本来触发堆栈缓冲区溢出
  • uc/os-II 原理及应用(八) 系统裁减以及移植到51单片机上
  • 掌握 Ubuntu 终端 mv 与 rename 命令的高效重命名使用方法
  • STM32-笔记42-实时时钟项目
  • uniapp 抖音小程序 getUserProfile:fail must be invoked by user tap gesture