条件变量(std::condition_variable)
这是c++11提供的一个类,用于多线程之间的通信。条件变量顾名思义就是根据不同条件而变化的量。
应用场景
这里举一个例子:有一个线程要求必须达到某种状态才能继续执行,否则将卡死在这里。在这种情况下我们有两种方案,第一种方案就是,在这个线程中不断去检测这个状态,直至符合条件。第二种方案就是,这个线程卡在这里不工作,当条件成立时,去通知这个线程。这与嵌入式中的中断系统有异曲同工之妙,条件变量就是应用于这第二个方案。
例子:
#include <iostream>
#include <thread>
#include <mutex>
#include <deque>
#include <condition_variable>
class A
{
public:
void WriteFunction()
{
for (int i = 0; i < 1000; ++i)
{
std::unique_lock<std::mutex> my_unique_lock(my_mutex_1);
std::cout << "向队列中添加一个元素" << std::endl;
my_deque.push_back(i);
}
}
void ReadFunction()
{
for (int i = 0; i < 1000; ++i)
{
std::unique_lock<std::mutex> my_unique_lock(my_mutex_1);
if (!my_deque.empty())
{
std::cout << "读出队列的第一个元素: " << my_deque.front() << std::endl;
my_deque.pop_front();
}
}
}
private:
std::deque<int> my_deque;
std::mutex my_mutex_1;
std::condition_variable my_condition_variable;
};
int main()
{
A a;
std::thread my_thread_1(&A::WriteFunction, std::ref(a));
std::thread my_thread_2(&A::ReadFunction, std::ref(a));
my_thread_1.join();
my_thread_2.join();
std::cout << "Hello World!\n";
}
上面的例子中,我们有两个线程,一个线程向队列中写数据,另一个线程从队列中读数据。读队列的线程需要判断队列是否为空,因为队列为空,读取是会出错的。如果向队列中写数据的线程在一段时间里没有进行,此时依旧会进入读队列的线程,并判断队列是否为空,这样严重浪费了CPU的资源。
这就需要使用上面提到的第二种方案,当队里为空时,读队列的线程就卡在那里,进入睡眠状态。如果写队列的线程向队列中写入数据后,就唤醒读队列的进程。
例子:
#include <iostream>
#include <thread>
#include <mutex>
#include <deque>
#include <condition_variable>
class A
{
public:
void WriteFunction()
{
for (int i = 0; i < 1000; ++i)
{
std::unique_lock<std::mutex> my_unique_lock(my_mutex_1);
std::cout << "向队列中添加一个元素" << std::endl;
my_deque.push_back(i);
my_condition_variable.notify_one();
}
}
void ReadFunction()
{
for (int i = 0; i < 1000; ++i)
{
std::unique_lock<std::mutex> my_unique_lock(my_mutex_1);
my_condition_variable.wait(my_unique_lock, [this]
{
return !my_deque.empty();
});
std::cout << "读出队列的第一个元素: " << my_deque.front() << std::endl;
my_deque.pop_front();
}
}
private:
std::deque<int> my_deque;
std::mutex my_mutex_1;
std::condition_variable my_condition_variable;
};
int main()
{
A a;
std::thread my_thread_1(&A::WriteFunction, std::ref(a));
std::thread my_thread_2(&A::ReadFunction, std::ref(a));
my_thread_1.join();
my_thread_2.join();
std::cout << "Hello World!\n";
}
解释上面例子中的函数:
my_condition_variable.wait()
这是一个重载的函数,有两种形式:
- 第一种形式
void wait(unique_lock<mutex>& _Lck, _Predicate _Pred)
第一个参数是一个uniq_lock对象,第二个参数_Predicate是一个函数指针。
功能:先将uniq_lock对象锁住,然后判断函数返回的值,如果为真,执行下面的语句。如果为假,,此线程在此处进入等待状态,然后解开锁。
- 第二种形式
void wait(unique_lock<mutex>& _Lck)
功能:先将uniq_lock对象锁住,此线程在此处进入等待状态,然后解开锁。
wait()的等待状态要等到什么时候呢?
my_condition_variable.notify_one()
如果在其他现充中运行了notify_one();函数,系统就会切入wait()的进程,等待状态就会被唤醒,先上锁,再运行下面的代码。
惊群效应
可参考这篇文章:https://blog.csdn.net/lyztyycode/article/details/78648798?locationNum=6&fps=1
如果多个线程都进入了等待状态,而此时.notify_one()了,就会出现著名的惊群效应。
notify_all()避免惊群效应
notify_all()可以唤醒所有正在等待的进程,并且让这些进程都运行wait()后面的代码!