天天看点

C++多线程(6)之条件变量(std::condition_variable)

条件变量(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()后面的代码!