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

Dav*_*aim 5 c++ multithreading synchronization condition-variable

我查看了VC++的实现std::condition_variable(lock,pred),基本上,它看起来像这样:

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

基本上,裸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_VARIABLEwait调用的类__crtSleepConditionVariableSRW.

进行一些汇编调试,__crtSleepConditionVariableSRW只需提取SleepConditionVariableSRW函数指针,然后调用它.

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

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

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

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

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

con*_*nio 3

你违反了合同,所以一切都失败了。请参阅: http: //en.cppreference.com/w/cpp/thread/condition_variable

\n

TLDR:当您持有互斥体时,谓词不可能被其他人更改。

\n

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

\n

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

\n

如果您遵守规则,可以:

\n
    \n
  1. 服务员首先持有互斥锁
  2. \n
  3. 进入std::condition_variable::wait. (回想一下,通知程序仍在等待互斥锁。)
  4. \n
  5. 检查谓词并发现它不成立。(回想一下,通知程序仍在等待互斥锁。)
  6. \n
  7. 调用一些实现定义的魔法来释放互斥锁并等待,只有现在通知程序才能继续。
  8. \n
  9. 通知者最终成功获取了互斥体。
  10. \n
  11. 通知程序会更改任何需要更改的内容以使谓词保持正确。
  12. \n
  13. 通知者调用std::condition_variable::notify_one.
  14. \n
\n

或者:

\n
    \n
  1. 通知者获取互斥体。(回想一下,服务员在尝试获取互斥体时被阻止。)
  2. \n
  3. 通知程序会更改任何需要更改的内容以使谓词保持正确。(回想一下,服务员仍然被挡着。)
  4. \n
  5. 通知者释放互斥锁。(在途中的某个地方,服务员会调用std::condition_variable::notify_one,但是一旦互斥体被释放......)
  6. \n
  7. 服务员获取互斥体。
  8. \n
  9. 服务员叫道std::condition_variable::wait
  10. \n
  11. 服务员检查while (!_Pred())中提琴!谓词为真。
  12. \n
  13. 服务员甚至不进入内部wait,因此通知者是否成功调用std::condition_variable::notify_one或尚未成功调用都无关紧要。
  14. \n
\n

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

\n
\n

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

\n
\n

请注意,这是条件变量的一般规则,而不是对std::condition_variabless(包括 Windows CONDITION_VARIABLE、POSIXpthread_cond_t等)的特殊要求。

\n
\n

回想一下,wait采用谓词的重载只是一个便利函数,因此调用者不必处理虚假唤醒。标准 (\xc2\xa730.5.1/15) 明确表示此重载相当于 Microsoft 实现中的 while 循环:

\n
\n

效果:相当于:

\n
while (!pred())\n    wait(lock);\n
Run Code Online (Sandbox Code Playgroud)\n
\n

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

\n
\n

Windows 关键部分和 Slim Reader/Writer Locks 作为用户模式设施而不是内核对象是无关紧要的,与问题无关。还有其他替代实现。如果您有兴趣了解 Windows 如何设法自动释放 CS/SRWL 并进入等待状态(使用互斥体和事件的天真的 pre-Vista 用户模式实现做了什么错误),那就是另一个问题了。

\n