引言
当谈到多线程编程时,同步操作是一个不可忽视的问题。为了实现线程之间的协调和通信,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++、算法和编程的奥秘。祝您生活愉快,排便顺畅!