std::condition_variable 在 std::condition_variable::notify_all() 从其他线程后未正确唤醒

Posted

技术标签:

【中文标题】std::condition_variable 在 std::condition_variable::notify_all() 从其他线程后未正确唤醒【英文标题】:std::condition_variable not properly wakes up after std::condition_variable::notify_all() from other thread 【发布时间】:2015-08-03 14:06:38 【问题描述】:

此代码是实际项目代码的简化。主线程创建工作线程并使用 std::condition_variable 等待工作线程真正启动。在以下代码中 std::condition_variable 在 current_thread_state 变为“ThreadState::Stopping”后唤醒 - 这是来自工作线程的第二次通知,即当 current_thread_state 变为“ThreadState::Starting”时主线程在第一次通知后没有唤醒”。结果陷入僵局。为什么会发生这种情况?为什么 std::condition_variable 在第一个 thread_event.notify_all() 之后没有唤醒?

int main()

  std::thread thread_var;
  struct ThreadState 
    enum Type  Stopped, Started, Stopping ;
  ;
  ThreadState::Type current_thread_state = ThreadState::Stopped;
  std::mutex thread_mutex;
  std::condition_variable thread_event;
  while (true) 
    
      std::unique_lock<std::mutex> lck(thread_mutex);
      thread_var = std::move(std::thread([&]() 
        
          std::unique_lock<std::mutex> lck(thread_mutex);
          cout << "ThreadFunction() - step 1\n";
          current_thread_state = ThreadState::Started;
        
        thread_event.notify_all();

        // This code need to disable output to console (simulate some work).
        cout.setstate(std::ios::failbit);
        cout << "ThreadFunction() - step 1 -> step 2\n";
        cout.clear();

        
          std::unique_lock<std::mutex> lck(thread_mutex);
          cout << "ThreadFunction() - step 2\n";
          current_thread_state = ThreadState::Stopping;
        
        thread_event.notify_all();
      ));

      while (current_thread_state != ThreadState::Started) 
        thread_event.wait(lck);
      
    

    if (thread_var.joinable()) 
      thread_var.join();
      current_thread_state = ThreadState::Stopped;
    
  
  return 0;

【问题讨论】:

【参考方案1】:

一旦你调用了notify_all 方法,你的主线程和你的工作线程(在完成它的工作之后)都试图获得thread_mutex 互斥锁上的锁。如果您的工作负载微不足道,例如在您的示例中,工作线程可能会在主线程之前获得锁,并在主线程读取它之前将状态设置回ThreadState::Stopped。这会导致死锁。

尝试增加大量工作负载,例如

std::this_thread::sleep_for( std::chrono::seconds( 1 ) );

到工作线程。现在死锁的可能性要小得多。当然,这不能解决您的问题。这只是为了说明问题。

【讨论】:

是的。你说得对。如果在工作线程 func 中注释 cout.setstate(std::ios::failbit),那么一切正常。但我不完全明白,为什么 thread_event.wait(lck) 在第一个“thread_event.notify_all()”之后没有醒来,因为 c++ 文档说:等待操作原子地释放互斥锁并暂停线程的执行。当通知条件变量时,线程被唤醒,并重新获取互斥体。 所以在这种情况下最简单的解决方案是将while (current_thread_state != ThreadState::Started)更改为while (current_thread_state == ThreadState::Stopped) @stefaanv,一定要找到状态ThreadState::Started,保证知道工作线程正在运行。第一次和第二次调用 thread_event.notify_all() 之间的工作线程中的代码可以执行任意时间或等待某个事件。 @soler:好的,我是针对发布的问题提出解决方案,而不是您的要求。无论如何,问题是你不能保证你能够检查确切的状态,所以你必须以某种方式处理它.. @Soler:关于你在第一条评论中的问题:是的,互斥体是获得的,基本上就像你会在它上面调用lock 方法一样。但是在试图获取互斥锁的两个线程之间存在竞争条件。哪个线程获得锁是未定义的。关于您的问题,您可以使用第二个condition_variable 或使用轮询。但从更高的角度分析您的实际需求时,可能会有更好的解决方案。【参考方案2】:

您有两个线程在竞争:一个写入两次current_thread_state 的值,另一个读取一次current_thread_state 的值。

不确定事件顺序是写-写-读还是写-读-写,两者都是您的应用程序的有效执行。

【讨论】:

以上是关于std::condition_variable 在 std::condition_variable::notify_all() 从其他线程后未正确唤醒的主要内容,如果未能解决你的问题,请参考以下文章

std::atomic 和 std::condition_variable 等待、notify_* 方法之间的区别

C++ std::condition_variable wait() wait_for() 区别 怎么用 实例

在等待std :: condition_variable时如何处理系统时钟更改?

排序多个线程如何在 std::condition_variable::notify_all 之后重新获取 std::unique_lock<std::mutex>

如何正确使用 std::condition_variable?

std::condition_variable::notify_all() - 我需要一个例子