Possible race condition in std::condition_variable?

喜你入骨 提交于 2019-12-04 06:58:04

You've violated the contract so all bets are off. See: http://en.cppreference.com/w/cpp/thread/condition_variable

TLDR: It's impossible for the predicate to change by someone else while you're holding the mutex.

You're supposed to change the underlying variable of the predicate while holding a mutex and you have to acquire that mutex before calling std::condition_variable::wait (both because wait releases the mutex, and because that's the contract).

In the scenario you described the change happened after the while (!_Pred()) saw that the predicate doesn't hold but before wait(_Lck) had a chance to release the mutex. This means that you changed the thing the predicate checks without holding the mutex. You have violated the rules and a race condition or an infinite wait are still not the worst kinds of UB you can get. At least these are local and related to the rules you violated so you can find the error...

If you play by the rules, either:

  1. The waiter takes hold of the mutex first
  2. Goes into std::condition_variable::wait. (Recall the notifier still waits on the mutex.)
  3. Checks the predicate and sees that it doesn't hold. (Recall the notifier still waits on the mutex.)
  4. Call some implementation defined magic to release the mutex and wait, and only now may the notifier proceed.
  5. The notifier finally managed to take the mutex.
  6. The notifier changes whatever needs to change for the predicate to hold true.
  7. The notifier calls std::condition_variable::notify_one.

or:

  1. The notifier acquires the mutex. (Recall that the waiter is blocked on trying to acquire the mutex.)
  2. The notifier changes whatever needs to change for the predicate to hold true. (Recall that the waiter is still blocked.)
  3. The notifier releases the mutex. (Somewhere along the way the waiter will call std::condition_variable::notify_one, but once the mutex is released...)
  4. The waiter acquires the mutex.
  5. The waiter calls std::condition_variable::wait.
  6. The waiter checks while (!_Pred()) and viola! the predicate is true.
  7. The waiter doesn't even go into the internal wait, so whether or not the notifier managed to call std::condition_variable::notify_one or didn't manage to do that yet is irrelevant.

That's the rationale behind the requirement on cppreference.com:

Even if the shared variable is atomic, it must be modified under the mutex in order to correctly publish the modification to the waiting thread.

Note that this is a general rule for condition variables rather than a special requirements for std::condition_variabless (including Windows CONDITION_VARIABLEs, POSIX pthread_cond_ts, etc.).


Recall that the wait overload that takes a predicate is just a convenience function so that the caller doesn't have to deal with spurious wakeups. The standard (§30.5.1/15) explicitly says that this overload is equivalent to the while loop in Microsoft's implementation:

Effects: Equivalent to:

while (!pred())
    wait(lock);

Does the simple wait work? Do you test the predicate before and after calling wait? Great. You're doing the same. Or are you questioning void std::condition_variable::wait( std::unique_lock<std::mutex>& lock ); too?


Windows Critical Sections and Slim Reader/Writer Locks being user-mode facilities rather than kernel objects is immaterial and irrelevant to the question. There are alternative implementations. If you're interested to know how Windows manages to atomically release a CS/SRWL and enter a wait state (what naive pre-Vista user-mode implementations with Mutexes and Events did wrong) that's a different question.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!