当谈到多线程编程时,同步操作是一个不可忽视的问题。为了实现线程之间的协调和通信,C++11引入了一组非常强大的同步原语,其中之一就是condition_variable(条件变量)。在本文中,我们将深入探讨condition_variable的使用方法和原理,我们将学习如何使用condition_variable来实现线程的等待和唤醒机制。让我们开始深入研究condition_variable吧!无论您是想了解更多关于线程同步的内容,还是希望提高自己在多线程编程方面的能力,本文都将为您提供有价值的知识和实际应用技巧。
condition_variable是C++11引入的一个同步原语,用于实现线程之间的等待和唤醒机制。它是一种条件变量,可以与mutex(互斥锁)结合使用,实现复杂的线程同步和通信。
condition_variable的主要作用是允许一个或多个线程等待某个条件满足后再继续执行。在等待期间,线程会被阻塞,不会消耗CPU资源,直到其他线程通过通知(notify)来唤醒它们。
头文件
wait函数是condition_variable类的成员函数,它的声明位于<condition_variable>头文件中。在使用wait函数时,需要包含该头文件。
函数原型
wait函数的原型如下:
void wait(unique_lock<mutex>& lock);
wait函数接受一个unique_lock<mutex>类型的引用作为参数。unique_lock是一个互斥锁的封装类,可以提供更灵活的加锁和解锁操作。
头文件
它的声明位于<condition_variable>头文件中。在使用notify_one
函数时,需要包含该头文件。
函数原型
notify_one
函数的原型如下:
void notify_one() noexcept;
notify_one
函数没有参数,且是一个noexcept
函数。
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++、算法和编程的奥秘。祝您生活愉快,排便顺畅!