condition_variable::notify_one 不立即解封等待?

condition_variable::notify_one does not instantly unblock wait?

我对 notify_one 函数有疑问。在以下代码中,

#include <iostream>
#include <condition_variable>
#include <thread>
#include <chrono>
#include <mutex>

std::condition_variable cv;
std::mutex m;
bool ready = false;

void f()
{
  std::unique_lock<std::mutex> lk(m);
  std::cout << "get into wait, ready=" << ready << std::endl;
  cv.wait(lk, []() { return ready; });
  std::cout << "get out of wait, ready=" << ready << std::endl;
}

int main()
{
  std::thread a(f);

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

  {
    std::unique_lock<std::mutex> lk(m, std::defer_lock);
    if (lk.try_lock()) {
      std::cout << "main try_lock success, ready=" << ready << std::endl;
      ready = true;
    }
  }
  std::cout << "main notify, ready=" << ready << std::endl;
  cv.notify_one();

  // std::cout << "hello" << std::endl;

  {
    std::unique_lock<std::mutex> lk(m, std::defer_lock);
    if (lk.try_lock()) {
      std::cout << "main try_lock success, ready=" << ready << std::endl;
      ready = true;
    }
  }
  std::cout << "main notify, ready=" << ready << std::endl;
  cv.notify_one();

  a.join();

  return 0;
}

我得到以下结果,

get into wait, ready=0
main try_lock success, ready=0
main notify, ready=1
main try_lock success, ready=1
main notify, ready=1
get out of wait, ready=1

但我希望得到下面的结果,因为根据 page,如果调用 notify_one,等待将被解除阻塞并重新获取 mutex(m) 的锁。

get into wait, ready=0
main try_lock success, ready=0
main notify, ready=1
main notify, ready=1
get out of wait, ready=1

我发现如果我注释掉 std::cout << "hello" << std::endl; 我会得到预期的结果。在我看来 notify_one 不会立即解除等待。正确吗?

非常感谢!

在您的第一个 cv.notify_one(); 之后,您没有同步点(直到 a.join();)并且线程已经 运行ning 很可能会继续 运行 并到达 a.join(); 在调度程序决定让另一个线程旋转之前。

作为实验,您可以通过在通知后稍微休眠来让出线程的执行槽 - 它可能导致输出的顺序符合您的预期。

cv.notify_one();
std::this_thread::sleep_for(std::chrono::milliseconds(1));

不过不要在生产代码中依赖它。这不能保证任何事情,因为线程之间没有同步(join() 除外)。

Looks to me like notify_one does not immediately unblock the wait. It is correct?

立即通知取消阻止等待,这意味着等待能够在通知后恢复。即休眠线程被标记为可运行。

但是,调度程序不一定会立即重新启动它。如果它必须抢占一些已经 运行 thread/process,它可能会等到 yield、系统调用或其他一些取消点,否则甚至不会查看新运行的线程,直到当前时间片结束。

解除阻塞与强制立即切换上下文不同(幸运的是,否则同步会更加昂贵)。