转载声明
————————————————
版权声明:本文为CSDN博主「陈硕」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Solstice/article/details/11432817
正文:
- 条件变量只有一种正确使用的方式,几乎不可能用错。对于 wait 端:
- 必须与 mutex 一起使用,该布尔表达式的读写需受此 mutex 保护。
- 在 mutex 已上锁的时候才能调用 wait()。
- 把判断布尔条件和 wait() 放到 while 循环中。
代码示例:
T take()
{
std::unique_lock<std::mutex> lock(mutex_);
// always use a while-loop, due to spurious wakeup
while (queue_.empty())
{
notEmpty_.wait(lock);
}
assert(!queue_.empty());
T front(queue_.front());
queue_.pop_front();
return front;
}
- 对于 signal/broadcast 端:
- 不一定要在 mutex 已上锁的情况下调用 signal (理论上)。
- 在 signal 之前一般要修改布尔表达式。
- 修改布尔表达式通常要用 mutex 保护(至少用作 full memory barrier)。
- 注意区分 signal 与 broadcast:“broadcast 通常用于表明状态变化,signal 通常用于表示资源可用。(broadcast should generally be used to indicate state change rather than resource availability。)”
代码示例:
void put(const T& x)
{
std::lock_guard<std::mutex> lock(mutex_);
queue_.push_back(x);
notEmpty_.notify_one();
}
为什么用while而不用if
- 因为可能某次操作系统唤醒 pthread_cond_wait 时 tasks.empty() 可能仍然为 true,言下之意就是操作系统可能会在一些情况下唤醒条件变量,即使没有其他线程向条件变量发送信号,等待此条件变量的线程也有可能会醒来。我们将条件变量的这种行为称之为 虚假唤醒 (spurious wakeup)。因此将条件(判断tasks.empty() 为true)放在一个 while 循环中意味着光唤醒条件变量不行,还必须条件满足程序才能继续执行正常的逻辑。
- 为什么会存在虚假唤醒呢?一个原因是
pthread_cond_wait 是 futex 系统调用,属于阻塞型的系统调用,当系统调用被信号中断的时候,会返回 -1,并且把 errno 错误码置为EINTR。很多这种系统调用为了防止被信号中断都会重启系统调用(即再次调用一次这个函数) - 除了上面的信号因素外,还存在以下情况:条件满足了发送信号,但等到调用 pthread_cond_wait 的线程得到 CPU 资源时,条件又再次不满足了。好在无论是哪种情况,醒来之后再次测试条件是否满足就可以解决虚假等待的问题。这就是使用 while 循环来判断条件,而不是使用 if 语句的原因。
事件等待器的实现:
#include <mutex>
#include <condition_variable>
class CountDownLatch
{
public:
explicit CountDownLatch(int count) :count_(count)
{
}
void wait()
{
std::unique_lock<std::mutex> lock(mutex_);
while (count_ > 0)
condition_.wait(lock);
}
void countDown()
{
std::unique_lock<std::mutex> lock(mutex_);
--count_;
if (count_ == 0)
condition_.notify_all();
}
private:
std::mutex mutex_;
std::condition_variable condition_;
int count_;
};