- iOS/Objective-C 元类和类别
- objective-c - -1001 错误,当 NSURLSession 通过 httpproxy 和/etc/hosts
- java - 使用网络类获取 url 地址
- ios - 推送通知中不播放声音
我研究了 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
不是原子函数。
标准是怎么说的?它真的是竞争条件吗?
最佳答案
你违反了契约(Contract),所以所有的赌注都被取消了。请参阅:http://en.cppreference.com/w/cpp/thread/condition_variable
TLDR:当您持有互斥锁时,谓词不可能被其他人更改。
您应该在持有互斥锁的同时更改谓词的基础变量并且您必须在调用std::condition_variable::wait
之前获取该互斥锁。 (既因为 wait
释放了互斥体,也因为那是契约)。
在您描述的场景中,更改发生在 之后 while (!_Pred())
看到谓词不成立但在 wait(_Lck)
之前有机会释放互斥量。这意味着您在不持有互斥体的情况下更改了谓词检查的内容。你违反了规则,竞争条件或无限等待仍然不是你能得到的最糟糕的 UB 类型。至少这些是本地的并且与您违反的规则相关,因此您可以找到错误...
如果您遵守规则,则:
std::condition_variable::wait
. (回想一下,通知程序仍在等待互斥量。)std::condition_variable::notify_one
.或:
std::condition_variable::notify_one
,但是一旦互斥量被释放......)std::condition_variable::wait
.while (!_Pred())
和 viola! 谓词为真。wait
,所以通知者是否成功调用了std::condition_variable::notify_one
或者还没有设法做到这一点是无关紧要的。这就是 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.
请注意,这是对条件变量的一般规则,而不是对 std::condition_variables
的特殊要求s(包括 Windows CONDITION_VARIABLE
s、POSIX pthread_cond_t
s 等)。
回想一下 wait
采用谓词的重载只是一个方便的函数,因此调用者不必处理虚假唤醒。标准 (§30.5.1/15) 明确指出此重载等同于 Microsoft 实现中的 while 循环:
Effects: Equivalent to:
while (!pred())
wait(lock);
做简单的wait
工作?你在调用wait
之前和之后测试谓词吗? ?伟大的。你也在做同样的事情。或者你在质疑void std::condition_variable::wait( std::unique_lock<std::mutex>& lock );
也是吗?
Windows Critical Sections 和 Slim Reader/Writer Locks 是用户模式设施而不是内核对象,这与问题无关紧要。有替代的实现。如果您有兴趣了解 Windows 如何设法以原子方式释放 CS/SRWL 并进入等待状态(使用互斥锁和事件的天真的 pre-Vista 用户模式实现做错了什么),那是另一个问题。
关于c++ - std::condition_variable 中可能的竞争条件?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42470773/
假设我正在使用 APC,其中过程和调用代码都使用 SetLastError 和 GetLastError。这会导致 GetLastError 产生不可预测的值。有什么办法可以解决这个问题吗? VOID
关闭。这个问题是opinion-based .它目前不接受答案。 想改善这个问题吗?更新问题,以便可以通过 editing this post 用事实和引文回答问题. 7年前关闭。 Improve t
任何人都可以,请告诉我,如何在不进行JavaScript轮询/ setInterval的情况下,在完整日历上填充/显示在服务器端动态更新的数据。 grails中提供了Atmosphere插件,但是文档
我正在尝试调整我的代码,从仅在前台使用 WCSessionDelegate 回调到在后台通过 handleBackgroundTasks: 接受 WKWatchConnectivityRefreshB
我正在构建批处理系统。 单位 的批处理数量从 20 到 1000 不等。每个 Unit 本质上都是模型的层次结构(一个主模型和许多子模型)。我的任务涉及将每个模型层次结构作为单个事务保存到数据库中(每
我拍了一张图片并将其切成三 block ,然后将它们向右浮动,让文字围绕它们流动。 HTML 看起来像这样: 在我添加侧边栏并将其 float 到图像的右上方之前,它工作正常,就像这样... T
我正在考虑嵌入式 Linux 项目(还没有硬件)中即将出现的情况,其中两个外部芯片需要共享一条物理 IRQ 线。这条线在硬件中能够实现边沿触发,但不能实现电平触发中断。 查看 Linux 中的共享 i
我观察到,当 linux futexes 发生争用时,系统会在自旋锁上花费大量时间。我注意到即使不直接使用 futex 也是一个问题,但在调用 malloc/free、rand、glib 互斥调用和其
我终于能够获得一些工具提示,最终可以使用以下代码: Hover over me 然后 $('[rel=tooltip]').tooltip(); 我遇到的问题是它使用 jQueryUI 工
我是一名优秀的程序员,十分优秀!