简体   繁体   English

std :: condition_variable中可能的竞争条件?

[英]Possible race condition in std::condition_variable?

I've looked into the VC++ implementation of std::condition_variable(lock,pred) , basically, it looks like this: 我已经研究了std::condition_variable(lock,pred)的VC ++实现,基本上,它看起来像这样:

template<class _Predicate>
        void wait(unique_lock<mutex>& _Lck, _Predicate _Pred)
        {   // wait for signal and test predicate
        while (!_Pred())
            wait(_Lck);
        }

Basically , the naked wait calls _Cnd_waitX which calls _Cnd_wait which calls do_wait which calls cond->_get_cv()->wait(cs); 基本上,裸wait调用_Cnd_waitX调用_Cnd_wait ,调用do_wait调用cond->_get_cv()->wait(cs); (all of these are in the file cond.c). (所有这些都在文件cond.c中)。

cond->_get_cv() returns Concurrency::details::stl_condition_variable_interface . cond->_get_cv()返回Concurrency::details::stl_condition_variable_interface

If we go to the file primitives.h , we see that under windows 7 and above, we have the class stl_condition_variable_win7 which contains the old good win32 CONDITION_VARIABLE , and wait calls __crtSleepConditionVariableSRW . 如果我们转到文件primitives.h ,我们看到在Windows 7及更高版本下,我们有类stl_condition_variable_win7 ,它包含旧的好的win32 CONDITION_VARIABLE ,并且wait调用__crtSleepConditionVariableSRW

Doing a bit of assembly debug, __crtSleepConditionVariableSRW just extract the the SleepConditionVariableSRW function pointer, and calls it. 做一些汇编调试, __crtSleepConditionVariableSRW只是提取SleepConditionVariableSRW函数指针,然后调用它。

Here's the thing: as far as I know, the win32 CONDITION_VARIABLE is not a kernel object, but a user mode one. 事情就是这样:据我所知,win32 CONDITION_VARIABLE不是内核对象,而是用户模式对象。 Therefore, if some thread notifies this variable and no thread actually sleep on it, you lost the notification, and the thread will remain sleeping until timeout has reached or some other thread notifies it. 因此,如果某个线程通知此变量并且没有线程实际上在其上休眠,则您丢失了通知,并且线程将保持休眠状态,直到超时或其他某个线程通知它。 A small program can actually prove it - if you miss the point of notification - your thread will remain sleeping although some other thread notified it. 一个小程序实际上可以证明它 - 如果你错过了通知点 - 你的线程将保持睡眠,虽然其他一些线程通知它。

My question goes like this: 我的问题是这样的:
one thread waits on a condition variable and the predicate returns false. 一个线程在条件变量上等待,谓词返回false。 Then, the whole chain of calls explained above takes place. 然后,发生上面解释的整个呼叫链。 In that time, another thread changed the environment so the predicate will return true and notifies the condition variable. 在那个时候,另一个线程改变了环境,因此谓词将返回true 通知条件变量。 We passed the predicate in the original thread, but we still didn't get into SleepConditionVariableSRW - the call chain is very long. 我们在原始线程中传递了谓词,但是我们仍然没有进入SleepConditionVariableSRW - 调用链非常长。

So, although we notified the condition variable and the predicate put on the condition variable will definitely return true (because the notifier made so), we are still blocking on the condition variable, possibly forever. 所以,虽然我们通知了条件变量并且条件变量上的谓词肯定会返回true(因为通知程序是这样做的),我们仍然会阻塞条件变量,可能永远。

Is this how should it behave? 这是怎么表现的? It seems like a big ugly race condition waiting to happen. 这似乎是一个巨大的丑陋竞争条件等待发生。 If you notify a condition variable and it's predicate returns true - the thread should unblock. 如果您通知条件变量并且它的谓词返回true - 则该线程应该解除阻塞。 But if we're in the limbo between checking the predicate and going to sleep - we are blocked forever. 但是,如果我们在检查谓词和睡觉之间处于不确定状态 - 我们将永远被阻止。 std::condition_variable::wait is not an atomic function. std::condition_variable::wait不是原子函数。

What does the standard says about it and is it a really race condition? 标准对此有何看法,是否真的是竞争条件?

You've violated the contract so all bets are off. 你违反了合同,所以所有的赌注都没有了。 See: http://en.cppreference.com/w/cpp/thread/condition_variable 请参阅: 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. TLDR:当你持有互斥锁时,谓词不可能由别人改变。

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). 您应该在持有互斥锁的同时更改谓词的基础变量, 并且必须在调用std::condition_variable::wait之前获取该互斥锁(两者都因为wait释放互斥锁,因为这是合同)。

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. 在方案中,您所描述的改变发生的while (!_Pred())看到,谓词不成立,但之前wait(_Lck)有机会释放互斥锁。 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. 你违反了规则,竞争条件或无限等待仍然不是你能得到的最糟糕的UB。 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 . 进入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. 通知程序会更改谓词的任何需要更改以保持为true。
  7. The notifier calls std::condition_variable::notify_one . 通知程序调用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. 通知程序会更改谓词的任何需要更改以保持为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...) (在某个地方,服务员将调用std::condition_variable::notify_one ,但一旦互斥锁被释放......)
  4. The waiter acquires the mutex. 服务员获得互斥锁。
  5. The waiter calls std::condition_variable::wait . 服务员调用std::condition_variable::wait
  6. The waiter checks while (!_Pred()) and viola! 服务员检查while (!_Pred())中提琴! 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. 服务员甚至没有进入内部wait ,因此无论通知者是否设法调用std::condition_variable::notify_one或者没有设法做到这一点都是无关紧要的。

That's the rationale behind the requirement on cppreference.com: 这是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_variables s (including Windows CONDITION_VARIABLE s, POSIX pthread_cond_t s, etc.). 请注意,这是条件变量的一般规则,而不是std::condition_variables s的特殊要求(包括Windows CONDITION_VARIABLE s,POSIX pthread_cond_t s等)。


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. 回想一下,带谓词的wait重载只是一个便利函数,因此调用者不必处理虚假的唤醒。 The standard (§30.5.1/15) explicitly says that this overload is equivalent to the while loop in Microsoft's implementation: 标准(§30.5.1/ 15)明确表示此重载等同于Microsoft实现中的while循环:

Effects: Equivalent to: 效果:相当于:

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

Does the simple wait work? 简单的wait是否有效? Do you test the predicate before and after calling wait ? 你在调用wait之前和之后测试谓词吗? Great. 大。 You're doing the same. 你也是这样做的。 Or are you questioning void std::condition_variable::wait( std::unique_lock<std::mutex>& lock ); 或者你在质疑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. Windows Critical Sections和Slim Reader / Writer Lock是用户模式工具而不是内核对象,与问题无关,无关紧要。 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. 如果您有兴趣知道Windows如何通过原子方式释放CS / SRWL并进入等待状态(使用Mutexes和Events的天真的Vista之前的用户模式实现错误),这是一个不同的问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM