c++11 std::notify_all 和虚假唤醒

f1m*_*sch 2 c++ locking condition-variable c++11 spurious-wakeup

使用 c++11。既然std::notify_all会导致虚假唤醒,那么为什么std::notify_all会保留而不是std::notify_one一直存在呢?std::notify_one 顺便说一句,可能会导致虚假唤醒吗?


阐述一下我的疑惑:

当我调用std::condition_variable.wait/wait_for/wait_untiland时std::notify_XXX,我的目的通常是实现线程同步。也就是说,更多的线程阻塞等待,直到另一个线程通知只有其中一个线程解除阻塞。

那么我就可以调用notify_one来实现这一点,但是为什么还有另一个notify_all,它的目的是什么,或者notify_all适合什么情况?在我的情况下,当我调用 时notify_all,它会唤醒所有等待线程,然后只有一个线程真正解除阻塞,而其他线程仍然阻塞,这是否称为虚假唤醒?如果notify_one也会调用虚假唤醒?

Ted*_*gmo 6

void std::condition_variable::wait(std::unique_lock<std::mutex>& lock);从\n thread.condition/8.3开始:

\n
\n

当通过调用notify_\xc2\xadone()或 或虚假地调用notify_\xc2\xadall()发出信号时,该函数将解除阻塞。

\n
\n

所以调用notify_\xc2\xadone()ornotify_\xc2\xadall()并不是先决条件。它可以在不调用任何一个的情况下解锁。

\n
\n

上面的引用来自“C++20 第一个发布后草案”,但自从首次为 C++11 编写以来一直保持不变。

\n
\n
\n

为什么还有另一个notify_all,它的用途是什么,或者notify_all适合什么情况?

\n
\n

当您希望所有等待线程解除阻塞时。实际情况是需要关闭时。如果线程被阻塞,wait它们将永远不会完成并且join()它们将挂起。

\n

带有谓词的示例表示它应该等到abortedistruequeue.empty()is false

\n
bool pop_from_queue(T& item) {\n    std::unique_lock<std::mutex> lock(mtx);\n    while(queue.empty() && not aborted) cv.wait(lock);\n    if(aborted) return false;        // time to shutdown\n    // else pick an item from the queue\n    item = std::move(queue.front());\n    queue.pop();\n    return true;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

当需要关闭时,另一个线程通常会执行以下操作:

\n
aborted = true;   // std::atomic<bool>\ncv.notify_all();\n
Run Code Online (Sandbox Code Playgroud)\n
\n

当我调用nitify_all时,它会唤醒所有等待线程,然后只有一个线程真正解锁,其他线程仍然阻塞,这是否称为虚假唤醒?

\n
\n

不。虚假唤醒是指随时可能发生的唤醒。如果您调用notify_all,等待的线程将按顺序全部唤醒 - 而不是虚假的。

\n
\n

如果notify_one也会调用虚假唤醒?

\n
\n

它可能会导致虚假唤醒,但这将是一个实现细节。最好的办法是接受线程可能随时唤醒的事实,并在唤醒时检查谓词。

\n
\n

我可以精确控制等待线程中的何处解除阻塞(谁condition_variable.wait在没有谓词的情况下调用)?

\n
\n

在不检查谓词的情况下,线程唯一确定的事情就是它已醒来。无论出于正确与否,现在都不会了。

\n