【C++入门到精通】condition_variable(条件变量)C++11 [ C++入门 ]

简介: 【C++入门到精通】condition_variable(条件变量)C++11 [ C++入门 ]

引言

当谈到多线程编程时,同步操作是一个不可忽视的问题。为了实现线程之间的协调和通信,C++11引入了一组非常强大的同步原语,其中之一就是condition_variable(条件变量)。在本文中,我们将深入探讨condition_variable的使用方法和原理,我们将学习如何使用condition_variable来实现线程的等待和唤醒机制。让我们开始深入研究condition_variable吧!无论您是想了解更多关于线程同步的内容,还是希望提高自己在多线程编程方面的能力,本文都将为您提供有价值的知识和实际应用技巧

一、condition_variable简介

1. 官方文档

⭕condition_variable官方文档

2. 概念简介

condition_variable是C++11引入的一个同步原语,用于实现线程之间的等待和唤醒机制。它是一种条件变量,可以与mutex(互斥锁)结合使用,实现复杂的线程同步和通信。

condition_variable的主要作用是允许一个或多个线程等待某个条件满足后再继续执行。在等待期间,线程会被阻塞,不会消耗CPU资源,直到其他线程通过通知(notify)来唤醒它们。

二、成员函数

1. wait() 函数

  • 头文件
    wait函数是condition_variable类的成员函数,它的声明位于<condition_variable>头文件中。在使用wait函数时,需要包含该头文件。
  • 函数原型
  • wait函数的原型如下:
void wait(unique_lock<mutex>& lock);

wait函数接受一个unique_lock<mutex>类型的引用作为参数。unique_lock是一个互斥锁的封装类,可以提供更灵活的加锁和解锁操作。

  • 参数解释
  • wait函数的参数是一个unique_lock类型的引用,用于保护共享数据结构。在调用wait函数之前,需要先获取该互斥锁。wait函数会将当前线程阻塞,并且会自动释放这个互斥锁。

2. notify_one() 函数

  • 头文件
  • 它的声明位于<condition_variable>头文件中。在使用notify_one函数时,需要包含该头文件。
  • 函数原型
    notify_one函数的原型如下:
void notify_one() noexcept;

notify_one函数没有参数,且是一个noexcept函数。

3.notify_all() 函数

notify_all函数是condition_variable类的成员函数,用于唤醒所有等待中的线程。

  • 头文件
    notify_all函数的声明位于<condition_variable>头文件中。在使用notify_all函数时,需要包含该头文件。
  • 函数原型
    notify_all函数的原型如下:
void notify_all() noexcept;

notify_all函数没有参数,且是一个noexcept函数。

三、使用示例

· 使用条件变量实现生产者 —— 消费者模型

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

std::queue<int> g_queue;
std::mutex g_mutex;
std::condition_variable g_cv;

void producer() {
    for (int i = 0; i < 10; ++i) {
        std::this_thread::sleep_for(std::chrono::seconds(1));

        std::unique_lock<std::mutex> lock(g_mutex);
        g_queue.push(i);

        std::cout << "Producer: " << i << std::endl;

        // 通知等待中的消费者线程
        g_cv.notify_one();
    }
}

void consumer(int id) {
    for (int i = 0; i < 5; ++i) {
        std::unique_lock<std::mutex> lock(g_mutex);

        // 等待条件满足
        g_cv.wait(lock, [] { return !g_queue.empty(); });

        int value = g_queue.front();
        g_queue.pop();

        std::cout << "Consumer " << id << ": " << value << std::endl;
    }
}

int main() {
    std::thread t1(producer);
    std::thread t2(consumer, 1);
    std::thread t3(consumer, 2);

    t1.join();
    t2.join();
    t3.join();

    return 0;
}

在这个示例中,我们创建了一个全局队列 g_queue,一个互斥锁 g_mutex 和一个条件变量 g_cv。生产者线程通过循环将数字放入队列中,并使用 notify_one() 通知等待中的消费者线程。消费者线程在循环中等待条件满足,并使用 wait() 在等待时释放互斥锁,直到收到生产者线程的通知后再次获得互斥锁。


你可以运行这个示例,观察生产者和消费者之间的交互。生产者每秒生产一个数字,并输出到屏幕上;消费者每次从队列中取出一个数字,并输出到屏幕上。注意,消费者线程可能在某些时刻会同时被唤醒,但只有一个消费者能够获取到互斥锁并处理数据

· 两个线程交替打印,一个打印奇数,一个打印偶数

下面的代码实现了一个使用两个线程交替打印奇数和偶数的功能。在主函数中调用了 two_thread_print() 函数来启动两个线程。

其中,线程 t1 负责打印偶数,线程 t2 负责打印奇数。通过互斥锁 mtx 和条件变量 c 来实现线程之间的同步和通信。变量 flag 用于控制线程打印奇数还是偶数的判断。

具体流程如下:

  • 线程 t1 循环打印偶数,每次打印完后将 flag 设置为 false,并调用 c.notify_one() 通知线程 t2
  • 线程 t2 循环打印奇数,每次打印完后将 flag 设置为 true,并调用 c.notify_one() 通知线程 t1

这样,两个线程就可以交替执行,按顺序打印出奇数和偶数。

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

void two_thread_print()
{
    std::mutex mtx;                 // 创建一个互斥锁
    std::condition_variable c;      // 创建一个条件变量
    int n = 100;                    // 打印的最大数值
    bool flag = true;               // 初始时打印偶数

    // 创建线程 t1,用于打印偶数
    std::thread t1([&](){
        int i = 0;
        while (i < n)
        {
            std::unique_lock<std::mutex> lock(mtx);   // 加锁,必须使用 unique_lock
            c.wait(lock, [&]()->bool{return flag; }); // 等待 flag 为 true
            std::cout << i << std::endl;              // 打印偶数
            flag = false;                             // 设置 flag 为 false,表示该轮打印奇数
            i += 2;                                   // 增加偶数计数器
            c.notify_one();                           // 通知线程 t2
        }
    });

    // 创建线程 t2,用于打印奇数
    std::thread t2([&](){
        int j = 1;
        while (j < n)
        {
            std::unique_lock<std::mutex> lock(mtx);   // 加锁,必须使用 unique_lock
            c.wait(lock, [&]()->bool{return !flag; });// 等待 flag 为 false
            std::cout << j << std::endl;              // 打印奇数
            flag = true;                              // 设置 flag 为 true,表示该轮打印偶数
            j += 2;                                   // 增加奇数计数器
            c.notify_one();                           // 通知线程 t1
        }
    });

    // 等待两个线程执行完毕
    t1.join();
    t2.join();
}

int main()
{
    two_thread_print(); // 启动两个线程交替打印奇数和偶数

    return 0;
}

温馨提示

感谢您对博主文章的关注与支持!另外,我计划在未来的更新中持续探讨与本文相关的内容,会为您带来更多关于C++以及编程技术问题的深入解析、应用案例和趣味玩法等。请继续关注博主的更新,不要错过任何精彩内容!

再次感谢您的支持和关注。期待与您建立更紧密的互动,共同探索C++、算法和编程的奥秘。祝您生活愉快,排便顺畅!


目录
相关文章
|
4月前
|
存储 C++
C++语言中指针变量int和取值操作ptr详细说明。
总结起来,在 C++ 中正确理解和运用 int 类型地址及其相关取值、设定等操纵至关重要且基础性强:定义 int 类型 pointer 需加星号;初始化 pointer 需配合 & 取址;读写 pointer 执向之处需配合 * 解引用操纵进行。
430 12
|
6月前
|
存储 安全 编译器
c++入门
c++作为面向对象的语言与c的简单区别:c语言作为面向过程的语言还是跟c++有很大的区别的,比如说一个简单的五子棋的实现对于c语言面向过程的设计思路是首先分析解决这个问题的步骤:(1)开始游戏(2)黑子先走(3)绘制画面(4)判断输赢(5)轮到白子(6)绘制画面(7)判断输赢(8)返回步骤(2) (9)输出最后结果。但对于c++就不一样了,在下五子棋的例子中,用面向对象的方法来解决的话,首先将整个五子棋游戏分为三个对象:(1)黑白双方,这两方的行为是一样的。(2)棋盘系统,负责绘制画面。
93 0
|
8月前
|
编译器 C++ 容器
【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)
C++11为C++带来了革命性变化,引入了列表初始化、右值引用、移动语义、类的新默认成员函数和lambda表达式等特性。列表初始化统一了对象初始化方式,initializer_list简化了容器多元素初始化;右值引用和移动语义优化了资源管理,减少拷贝开销;类新增移动构造和移动赋值函数提升性能;lambda表达式提供匿名函数对象,增强代码简洁性和灵活性。这些特性共同推动了现代C++编程的发展,提升了开发效率与程序性能。
306 12
|
10月前
|
存储 缓存 C++
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
C++ 标准模板库(STL)提供了一组功能强大的容器类,用于存储和操作数据集合。不同的容器具有独特的特性和应用场景,因此选择合适的容器对于程序的性能和代码的可读性至关重要。对于刚接触 C++ 的开发者来说,了解这些容器的基础知识以及它们的特点是迈向高效编程的重要一步。本文将详细介绍 C++ 常用的容器,包括序列容器(`std::vector`、`std::array`、`std::list`、`std::deque`)、关联容器(`std::set`、`std::map`)和无序容器(`std::unordered_set`、`std::unordered_map`),全面解析它们的特点、用法
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
|
9月前
|
存储 分布式计算 编译器
C++入门基础2
本内容主要讲解C++中的引用、inline函数和nullptr。引用是变量的别名,与原变量共享内存,定义时需初始化且不可更改指向对象,适用于传参和返回值以提高效率;const引用可增强代码灵活性。Inline函数通过展开提高效率,但是否展开由编译器决定,不建议分离声明与定义。Nullptr用于指针赋空,取代C语言中的NULL。最后鼓励持续学习,精进技能,提升竞争力。
|
10月前
|
存储 机器学习/深度学习 编译器
【C++终极篇】C++11:编程新纪元的神秘力量揭秘
【C++终极篇】C++11:编程新纪元的神秘力量揭秘
|
存储 安全 编译器
【C++打怪之路Lv1】-- 入门二级
【C++打怪之路Lv1】-- 入门二级
130 0
|
自然语言处理 编译器 C语言
【C++打怪之路Lv1】-- C++开篇(入门)
【C++打怪之路Lv1】-- C++开篇(入门)
175 0
|
10月前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。