6 - 条件变量

条件变量允许一个线程堵塞,直到另一个线程设置某个条件或系统时间到达某个指定的时间。条件变量允许显式的线程间通信,如果熟悉Win32API的多线程编程,可将条件变量和Windows种的事件对象进行比较。

条件变量

简介

有两类条件变量,它们都定义在<condition_variable>中。

  • std::condition_variable:只能等待unique_lock<mutex>上的条件变量,在特定平台上的效率高。
  • std::condition_variable_any:可等待任何对象的条件变量,包括自定义锁类型。

方法

条件变量condition_variable类支持如下方法:

  • notify_one():唤醒等待该条件变量的线程之一。
  • notify_all():唤醒等待该条件变量的所有线程。
  • wait(unique_lock<mutex>& lk):使用lk锁住该线程并阻塞,直到另一个线程唤醒它。
  • wait_for(unique_lock<mutex>& lk, const chrono::duration<Rep, Period>& rel_time):和wait()类似,但超过给定的相对时间后也能解除该线程阻塞。
  • wait_until(unique_lock<mutex>& lk, const chrono::duration<Clock, Duration>& abs_time):和wait()类似,但超过给定的绝对时间后解除该线程阻塞。

condition_variable_any类也支持上述方法,但它可接收任意实现了lock()unlock()方法的锁。

需要注意的是,即使没有其他线程调用任何通知方法,线程也有可能会醒过来。因此当线程等待一个条件变量并醒过来后,需要检查它是否通过通知方法醒过来。

使用

条件变量可用于处理队列项的后台线程:在队列中插入要处理的项,后台线程等待队列中出现项,有的话让线程苏醒处理,没有就继续休眠。

假设有如下队列:

queue<string> m_queue;

需要确保在任何时候只有一个线程修改这个队列,可用互斥体实现这一点:

mutex m_mutex;

为了能在添加一项时通知后台线程,还需要一个条件变量:

condition_variable m_cond_var;

如果要往队列里添加一项,需要先获得互斥体上的锁,然后往队列中添加项,最后通知后台线程:

unique_lock lock(m_mutex);
m_queue.push("Thing");
m_cond_var.notify_all();

后台线程在一个无尽循环中等待通知,这里使用带有谓词的wait(),队列不空时才苏醒:

unique_lock lock(m_mutex);
while (true)
{
	m_cond_var.wait(lock, [this] {return !m_queue.empty(); });
	// 处理项......
	string item = m_queue.front();
}

需要确保代码中不会出现死锁。

参考资料

  • C++20高级编程