C++ 避免在条件更新为阻塞功能时丢失唤醒

C++ 避免在条件更新为阻塞功能时丢失唤醒,c++,condition-variable,C++,Condition Variable,我正在编写一个事件循环,它通过等待“work to do”条件变量(work to do)在没有工作要做时进入睡眠状态。根据不同的事件,不同的线程可以通知此条件变量。当一个事件在另一个线程中发生时,它会通知条件变量,唤醒事件循环,然后检查可能触发通知的条件,循环,直到没有更多的工作要做,然后再次等待。其中一个条件由阻塞函数(WaitForMessage())设置 事件循环线程: std::lock_guard<std::mutex> lock(work_to_do_lock); fo

我正在编写一个事件循环,它通过等待“work to do”条件变量(
work to do
)在没有工作要做时进入睡眠状态。根据不同的事件,不同的线程可以通知此条件变量。当一个事件在另一个线程中发生时,它会通知条件变量,唤醒事件循环,然后检查可能触发通知的条件,循环,直到没有更多的工作要做,然后再次等待。其中一个条件由阻塞函数(
WaitForMessage()
)设置

事件循环线程:

std::lock_guard<std::mutex> lock(work_to_do_lock);
for (;;) {
  if (condition1) {
    // Act on condition 1.
  } else if (condition2) {
    // Act on condition 2.
  } else if (HasMessage()) {
    // Act on receiving message.
  } else {
    work_to_do.wait(lock);
  }
}
主线程在进入事件循环之前获取保护条件变量的互斥锁(
work\u to\u do\u lock
),并在没有工作要做时将其传递到
wait()
调用。为了避免丢失唤醒,常见的建议是所有通知程序在更新其条件状态时必须持有锁。但是,如果要使用
work\u to\u do\u lock
保护
WaitForMessage()
调用,则可以防止其他信号唤醒事件循环

我提出的解决方案是在
WaitForMessage()
之后但在
notify_one()之前获取并释放锁:

检查新条件谓词的对应事件循环:

std::lock_guard<std::mutex> lock(work_to_do_lock);
for (;;) {
  if (condition1) {
    // Act on condition 1.
  } else if (condition2) {
    // Act on condition 2.
  } else if (has_message) {
    has_message = false;
    // Act on receiving message.
  } else {
    work_to_do.wait(lock);
  }
}
std::锁紧防护锁(工作锁紧);
对于(;;){
如果(条件1){
//根据条件1采取行动。
}否则如果(条件2){
//根据条件2采取行动。
}else if(有_消息){
has_message=false;
//在收到信息时采取行动。
}否则{
工作要做。等待(锁定);
}
}

我以前从未见过前一种方法,因此我想知道该设计是否存在缺陷,或者它通常被避免的原因是什么?似乎可以使用此方法作为在条件状态更新之前锁定条件变量锁的一般替代方法,假设特定的条件状态写/读本身受到某种互斥机制的保护。

您的方法可以工作,但它的效率低于重用任何同步可以安全地同时调用
WaitFormMessage
HasMessage
的方法(或者,换言之,将您的
work\u带到\u do\u lock
更新
HasMessage
值,而不是(比如)使用原子)当然,如果这段代码无法访问,这大概是您所能做的最好的了,因为您需要对其他条件进行互斥。

您的设计中的缺陷是,您的
条件变量::wait
没有谓词(可能没有任何超时)。是解释此问题的文章。那么您的谓词可能是由工作人员设置的某个原子标志,您不应该有任何问题。@pptaszni您的帖子链接确定了两个潜在问题:虚假唤醒和丢失唤醒。在我的帖子中,我描述了为什么我认为此设计会阻止后者,而对于前者,则是谓词检查在
if
/
else if
检查继续执行等待条件变量的
else
(在无条件for循环内部,因此在等待之前和之后始终检查它们)。只要避免了这两个条件,则调用
wait()
是安全的(即使没有谓词或超时)。好吧,您的示例似乎处理了虚假唤醒,但不是以推荐的方式,这意味着您仍然可以重新获得锁,检查所有条件,然后再次调用wait。是否可以丢失唤醒取决于您的其他工作人员,他们是否在锁下设置了正确的标志。具有
WaitFormMessage
的工作人员看起来不错。我更喜欢设置(大)超时以查看错误消息,而不是看到我的应用程序被阻止,以防我出错。无论如何,只要使用谓词等待,就可以避免麻烦。@pptaszni我不同意这不是处理虚假唤醒的推荐方法。如果您阅读了
wait()谓词版本的实现源代码
您将看到它只是将常规等待封装在测试谓词函数的while循环中。您在我的代码中说的语句“您仍然可以重新获得锁,遍历所有条件,然后再次调用wait”也正是
wait()的意思
使用谓词也可以,没有性能差异。事实上,使用谓词会浪费工作,因为我需要明确检查每个条件以执行分派。
for (;;) {
  // Wait for message to be received (blocking). Once it returns you are
  // guaranteed that HasMessage() will return true.
  WaitForMessage();

  {
    std::lock_guard<std::mutex> lock(work_to_do_lock);
  }

  // Wake-up the main event loop.
  work_to_do.notify_one();
}
for (;;) {
  // Wait for message to be received (blocking). Once it returns you are
  // guaranteed that HasMessage() will return true.
  WaitForMessage();

  {
    std::lock_guard<std::mutex> lock(work_to_do_lock);
    has_message = true;
  }

  // Wake-up the main event loop.
  work_to_do.notify_one();
}
std::lock_guard<std::mutex> lock(work_to_do_lock);
for (;;) {
  if (condition1) {
    // Act on condition 1.
  } else if (condition2) {
    // Act on condition 2.
  } else if (has_message) {
    has_message = false;
    // Act on receiving message.
  } else {
    work_to_do.wait(lock);
  }
}