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_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
\nTLDR:当您持有互斥体时,谓词不可能被其他人更改。
\n您应该在持有互斥体的同时更改谓词的基础变量,并且必须在调用之前获取该互斥体std::condition_variable::wait(都是因为wait释放互斥体,又因为那是契约)。
在您描述的场景中,更改发生在看到while (!_Pred())谓词不成立之后但之前wait(_Lck)有机会释放互斥体之前。这意味着您更改了谓词检查的内容而无需持有互斥锁。你违反了规则,竞争条件或无限等待仍然不是你能得到的最糟糕的 UB。至少这些是本地的并且与您违反的规则相关,因此您可以找到错误......
如果您遵守规则,可以:
\nstd::condition_variable::wait. (回想一下,通知程序仍在等待互斥锁。)std::condition_variable::notify_one.或者:
\nstd::condition_variable::notify_one,但是一旦互斥体被释放......)std::condition_variable::wait。while (!_Pred())和中提琴!谓词为真。wait,因此通知者是否成功调用std::condition_variable::notify_one或尚未成功调用都无关紧要。这就是 cppreference.com 上的要求背后的基本原理:
\n\n\n即使共享变量是原子的,也必须在互斥锁下对其进行修改,才能正确地将修改发布到等待线程。
\n
请注意,这是条件变量的一般规则,而不是对std::condition_variabless(包括 Windows CONDITION_VARIABLE、POSIXpthread_cond_t等)的特殊要求。
回想一下,wait采用谓词的重载只是一个便利函数,因此调用者不必处理虚假唤醒。标准 (\xc2\xa730.5.1/15) 明确表示此重载相当于 Microsoft 实现中的 while 循环:
\n\n效果:相当于:
\nRun Code Online (Sandbox Code Playgroud)\nwhile (!pred())\n wait(lock);\n
简单的wait有效吗?您在调用之前和之后测试谓词吗wait?伟大的。你也在做同样的事情。或者你void std::condition_variable::wait( std::unique_lock<std::mutex>& lock );也在质疑?
Windows 关键部分和 Slim Reader/Writer Locks 作为用户模式设施而不是内核对象是无关紧要的,与问题无关。还有其他替代实现。如果您有兴趣了解 Windows 如何设法自动释放 CS/SRWL 并进入等待状态(使用互斥体和事件的天真的 pre-Vista 用户模式实现做了什么错误),那就是另一个问题了。
\n