【问题标题】:condition_variable usage for signaling and waitingcondition_variable 用于信令和等待
【发布时间】:2021-07-03 19:58:14
【问题描述】:

如果数据竞争不是问题,我可以使用 std::condition_variable 来启动(即发信号)和停止(即等待)工作线程吗?

例如:

std::atomic<bool> quit = false;
std::atomic<bool> work = false;
std::mutex mtx;
std::condition_variable cv;

// if work, then do computation, otherwise wait on work (or quit) to become true
// thread reads: work, quit
void thread1()
{
   while ( !quit )
   {
      // limiting the scope of the mutex
      {
         std::unique_lock<std::mutex> lck(mtx);

         // I want here is to wait on this lambda
         cv.wait(lck, []{ return work || quit; });
      }

      if ( work )
      {
         // work can become false again while working.
         // I want here is to complete the work
         // then wait on the next iteration.
         ComputeWork();
      }
   }
}

// work controller
// thread writes: work, quit
void thread2()
{
   if ( keyPress == '1' )
   {
      // is it OK not to use a mutex here?
      work = false;
   }
   else if ( keyPress == '2' )
   {
      // ... or here?
      work = true;
      cv.notify_all();
   }
   else if ( keyPress == ESC )
   {
      // ... or here?
      quit = true;
      cv.notify_all();
   }
}

更新/总结:由于 Adam 描述的“丢失唤醒”场景,不安全。 cv.wait(lck, predicate()); 可以等价写成while(!predicate()){ cv.wait(lck); }

更容易看到问题:while(!predicate()){ /*lost wakeup can occur here*/ cv.wait(lck); }

可以通过将谓词变量的任何读/写操作放在互斥范围内来修复:

void thread2()
{
   if ( keyPress == '1' )
   {
      std::unique_lock<std::mutex> lck(mtx);
      work = false;
   }
   else if ( keyPress == '2' )
   {
      std::unique_lock<std::mutex> lck(mtx);
      work = true;
      cv.notify_all();
   }
   else if ( keyPress == ESC )
   {
      std::unique_lock<std::mutex> lck(mtx);
      quit = true;
      cv.notify_all();
   }
}

【问题讨论】:

  • 你应该没问题,因为读取原子会导致内存负载,因此每次条件变量检查其谓词时都会获取互斥锁。我确实遇到过一次,非常早在 MSVC 对 condition_variable 的支持中,它在读取条件变量检查的谓词内部的原子之前没有正确添加内存栅栏(并且错误的代码生成最终消失了)。
  • 条件变量的全部意义在于以原子方式唤醒等待线程,避免任何形式的休眠。您的更新不是理想的解决方案。您最初是在正确的路线上,但有必要在更改标志后通知条件变量。

标签: c++ multithreading concurrency mutex condition-variable


【解决方案1】:

不,不安全。等待的线程可以获取互斥体,检查谓词,看不到任何需要唤醒的东西。然后信号线程设置布尔值和信号。接下来,等待的线程在 cv 上阻塞,并且永远不会唤醒。

您必须在触发唤醒 lambda 条件和通知 cv 之间的某个时间点持有互斥锁,以避免这种情况。

“关闭”情况(关闭唤醒)我没有看过,它可能取决于究竟什么行为是可以的。如果没有正式意义上的规定,我也不会这样做。一般来说,在摆弄多线程代码时,您至少应该尝试正式的正确性证明草图,否则您的代码最多会意外工作。

如果你做不到,找一个可以为你编写代码的人。

【讨论】:

  • 这听起来像是“丢失的唤醒”,可以通过使用 while(!predicate()){Sleep(long period);等待(lck);}。修复它看起来像读取/写入标志的代码需要在互斥锁中。你能扩展“正确性的正式证明草图”吗?
猜你喜欢
  • 1970-01-01
  • 2020-11-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-28
  • 2014-02-20
  • 2020-11-01
  • 2014-03-12
相关资源
最近更新 更多