繁体   English   中英

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

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

我已经研究了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);
        }

基本上,裸wait调用_Cnd_waitX调用_Cnd_wait ,调用do_wait调用cond->_get_cv()->wait(cs); (所有这些都在文件cond.c中)。

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

如果我们转到文件primitives.h ,我们看到在Windows 7及更高版本下,我们有类stl_condition_variable_win7 ,它包含旧的好的win32 CONDITION_VARIABLE ,并且wait调用__crtSleepConditionVariableSRW

做一些汇编调试, __crtSleepConditionVariableSRW只是提取SleepConditionVariableSRW函数指针,然后调用它。

事情就是这样:据我所知,win32 CONDITION_VARIABLE不是内核对象,而是用户模式对象。 因此,如果某个线程通知此变量并且没有线程实际上在其上休眠,则您丢失了通知,并且线程将保持休眠状态,直到超时或其他某个线程通知它。 一个小程序实际上可以证明它 - 如果你错过了通知点 - 你的线程将保持睡眠,虽然其他一些线程通知它。

我的问题是这样的:
一个线程在条件变量上等待,谓词返回false。 然后,发生上面解释的整个呼叫链。 在那个时候,另一个线程改变了环境,因此谓词将返回true 通知条件变量。 我们在原始线程中传递了谓词,但是我们仍然没有进入SleepConditionVariableSRW - 调用链非常长。

所以,虽然我们通知了条件变量并且条件变量上的谓词肯定会返回true(因为通知程序是这样做的),我们仍然会阻塞条件变量,可能永远。

这是怎么表现的? 这似乎是一个巨大的丑陋竞争条件等待发生。 如果您通知条件变量并且它的谓词返回true - 则该线程应该解除阻塞。 但是,如果我们在检查谓词和睡觉之间处于不确定状态 - 我们将永远被阻止。 std::condition_variable::wait不是原子函数。

标准对此有何看法,是否真的是竞争条件?

你违反了合同,所以所有的赌注都没有了。 请参阅: http//en.cppreference.com/w/cpp/thread/condition_variable

TLDR:当你持有互斥锁时,谓词不可能由别人改变。

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

在方案中,您所描述的改变发生的while (!_Pred())看到,谓词不成立,但之前wait(_Lck)有机会释放互斥锁。 这意味着您更改了谓词检查的内容而不保留互斥锁。 你违反了规则,竞争条件或无限等待仍然不是你能得到的最糟糕的UB。 至少这些是本地的并且与您违反的规则相关,因此您可以找到错误...

如果您遵守规则,可以:

  1. 服务员首先抓住互斥锁
  2. 进入std::condition_variable::wait (回想一下通知程序仍在等互斥锁上。)
  3. 检查谓词并发现它不成立。 (回想一下通知程序仍在等互斥锁上。)
  4. 调用一些实现定义的魔法来释放互斥锁并等待,只有现在才能通知。
  5. 通知终于设法取了互斥量。
  6. 通知程序会更改谓词的任何需要更改以保持为true。
  7. 通知程序调用std::condition_variable::notify_one

要么:

  1. 通知程序获取互斥锁。 (回想一下,服务员在尝试获取互斥锁时被阻止。)
  2. 通知程序会更改谓词的任何需要更改以保持为true。 (回想一下,服务员仍然被阻止。)
  3. 通知程序释放互斥锁。 (在某个地方,服务员将调用std::condition_variable::notify_one ,但一旦互斥锁被释放......)
  4. 服务员获得互斥锁。
  5. 服务员调用std::condition_variable::wait
  6. 服务员检查while (!_Pred())中提琴! 谓词是真的。
  7. 服务员甚至没有进入内部wait ,因此无论通知者是否设法调用std::condition_variable::notify_one或者没有设法做到这一点都是无关紧要的。

这是cppreference.com要求背后的基本原理:

即使共享变量是原子的,也必须在互斥锁下对其进行修改,以便将修改正确地发布到等待的线程。

请注意,这是条件变量的一般规则,而不是std::condition_variables s的特殊要求(包括Windows CONDITION_VARIABLE s,POSIX pthread_cond_t s等)。


回想一下,带谓词的wait重载只是一个便利函数,因此调用者不必处理虚假的唤醒。 标准(§30.5.1/ 15)明确表示此重载等同于Microsoft实现中的while循环:

效果:相当于:

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

简单的wait是否有效? 你在调用wait之前和之后测试谓词吗? 大。 你也是这样做的。 或者你在质疑void std::condition_variable::wait( std::unique_lock<std::mutex>& lock ); 太?


Windows Critical Sections和Slim Reader / Writer Lock是用户模式工具而不是内核对象,与问题无关,无关紧要。 还有其他实施方案。 如果您有兴趣知道Windows如何通过原子方式释放CS / SRWL并进入等待状态(使用Mutexes和Events的天真的Vista之前的用户模式实现错误),这是一个不同的问题。

暂无
暂无

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

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