C++ 原子内存顺序与诸如 notify() 之类的线程事件
Posted
技术标签:
【中文标题】C++ 原子内存顺序与诸如 notify() 之类的线程事件【英文标题】:C++ atomic memory order vs thread events such as notify() 【发布时间】:2016-04-08 17:28:02 【问题描述】:一个原子布尔“就绪”在两个线程之间共享。线程 A 正在执行某项任务,线程 B 正在等待 A 通知。任务完成后,A 会将“就绪”设置为 True。
B 有一个等待谓词来测试“就绪”。醒来时,如果“就绪”仍然为 False,B 将返回等待。 (这可以防止虚假唤醒造成麻烦)
A 完成其任务,将“就绪”设置为 True,然后通知 B。
问题:我如何保证 B 将读取(加载)“准备好”为 True 而不是旧的 False?
atomic<bool> ready = false;
Thread A
do_something();
ready = true;
B_cond_var.notify_one();
Thread B:
B_cond_var.wait( mtx, []() -> bool return ready == true; );
do_something(); // <- sometimes doesn't happen
我发现有时 B 将“准备好”读为 False,并且永远不会醒来。这意味着从它的角度来看,通知是在更新之前来的,即使在 A 中顺序是更新,然后通知。
注意:我已经阅读了太多关于内存顺序保证的内容,并且我认为我对读写顺序的了解已经足够了。然而这个问题涉及到内存更新和线程通信事件的排序,即通知。我找不到关于如何保证这些订单的直接答案。
参见例如: http://www.developerfusion.com/article/138018/memory-ordering-for-atomic-operations-in-c0x/
http://bartoszmilewski.com/2008/12/01/c-atomics-and-memory-ordering/
Memory ordering behavior of std::atomic::load
另见 C++11 标准第 29.3 节
更新:抱歉造成混淆,以上内容是纯伪代码,是从更大的正文中总结出来的,我省略了不涉及问题的内容,例如互斥体的存在在条件变量上。工作代码展示了所描述的变量更新重新排序,并且大约每 400 天运行时通知一次——实际上每天 3-4 次。这正在实际发生。
【问题讨论】:
我认为你的 lambda 中缺少的只是return
。您的编译器应该对此发出警告;它使 lambda 每次都返回一个基本上随机的值。
@Cameron 我的错误,上面是玩具伪代码将问题从一个更大的主体中归结为一个更大的主体,而不是实际的工作代码。我描述的事件大约每运行 400 天发生一次
对B_cond_var.notify_one()
的调用与对atomic<bool> ready
的访问无关(我的理解是atomic<>
实例仅根据它们自身进行排序)。我相信您在更新ready
时需要持有互斥锁,这可能会消除ready
原子化带来的任何好处。
【参考方案1】:
如果您使用条件变量保护它,则不需要原子变量。但是,条件变量需要互斥体,我在这段代码中看不到互斥体。您的 lambda 也未命中 return 语句。
正确使用条件变量会给你想要的结果。
这里的内存排序完全无关紧要,因为所有 Posix 线程同步原语都会施加完整的内存屏障。
【讨论】:
您是说 notify_one 就总排序而言等同于内存更新?如果是这样,这是我第一次明确地读到这句话。如果是真的,我想知道 B 怎么可能无法正常醒来。 @bulletsweetp,互斥锁是。而且由于条件变量总是与互斥锁并排使用,因此它们具有相同的属性。在实际问题上,您需要提供确切的代码让我告诉您为什么它不起作用。现在您的代码不可编译。事实上,根据标准,cond vars 本身也施加了总顺序。 我会将其标记为答案——我在您的评论中寻找的是“cond vars 确实强加总顺序”以及原子内存读/写的事实。那是我在标准或其他任何地方都找不到的。顺便说一句,这个事实表明我关于出了什么问题的理论是不正确的,我后来发现了真正的错误。以上是关于C++ 原子内存顺序与诸如 notify() 之类的线程事件的主要内容,如果未能解决你的问题,请参考以下文章