C++11内存_顺序_获取和内存_顺序_释放语义?

2024-04-19

http://en.cppreference.com/w/cpp/atomic/memory_order http://en.cppreference.com/w/cpp/atomic/memory_order以及其他 C++11 在线参考,将 memory_order_acquire 和 memory_order_release 定义为:

  • 获取操作:无reads在当前线程中可以在此加载之前重新排序。
  • 释放操作:无writes在当前线程中可以在此存储之后重新排序。

这似乎允许执行获取后写入before获取操作,这对我来说似乎很奇怪(通常获取/释放操作语义限制all内存操作)。

相同的在线来源(http://en.cppreference.com/w/cpp/atomic/atomic_flag http://en.cppreference.com/w/cpp/atomic/atomic_flag)建议可以使用 C++ 原子和上述宽松的内存排序规则来构建自旋锁互斥锁:

lock mutex: while (lock.test_and_set(std::memory_order_acquire))

unlock mutex: lock.clear(std::memory_order_release);               

通过这种锁定/解锁的定义,如果 memory_order_acquire/release 确实是这样定义的(即不禁止获取后写入的重新排序),那么下面的简单代码不会被破坏吗:

Thread1:
  (0) lock
    (1) x = 1;
    (2) if (x != 1) PANIC
  (3) unlock

Thread2:
  (4) lock
    (5) x = 0;
  (6) unlock

是否可以执行以下操作:(0) 锁定、(1) x = 1、(5) x = 0、(2) PANIC ?我错过了什么?


自旋锁互斥体的实现对我来说看起来不错。我认为他们得到了定义acquire and release完全错误的。

这是我所知道的获取/释放一致性模型的最清晰的解释:加拉乔卢;莱诺斯基;劳登;长臂猿;古普塔; Hennessy:可扩展共享内存多处理器中的内存一致性和事件排序,国际症状比较拱门, ISCA(17):15-26, 1990, doi 10.1145/325096.325102 http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.17.8112。 (doi 位于 ACM 付费墙后面。实际链接是副本not在付费专区后面。)

查看第 3.3 节中的条件 3.1 和附图 3:

  • 在允许普通加载或存储访问之前 相对于任何其他处理器执行, 必须执行所有先前的获取访问,并且
  • 在允许执行释放访问之前 尊重任何其他处理器,所有以前的普通 必须执行加载和存储访问,并且
  • 特殊访问[顺序]与尊重一致 彼此。

The point is this: acquires and releases are sequentially consistent1 (all threads globally agree on the order in which acquires and releases happened.) All threads globally agree that the stuff that happens between an acquire and a release on a specific thread happened between the acquire and release. But normal loads and stores after a release are allowed to be moved (either by hardware or the compiler) above the release, and normal loads and stores before an acquire are allowed to be moved (either by hardware or the compiler) to after the acquire.

(脚注 1:这适用于most实现,但对于 ISO C++ 总体来说有点夸大其词。读者线程可以对另外 2 个线程完成的 2 个存储的顺序有不同意见。看使用 4 个线程获取/释放语义 https://stackoverflow.com/questions/48383867/acquire-release-semantics-with-4-threads, and 这个答案 https://stackoverflow.com/questions/27807118/will-two-atomic-writes-to-different-locations-in-different-threads-always-be-see/50679223#50679223有关如何为 POWER CPU 编译 C++ 的详细信息,演示了释放和获取在实践中的差异,但 seq_cst 没有。但大多数 CPU 仅通过一致性缓存在内核之间获取数据,这意味着全局顺序确实存在。)


In the C++标准 http://isocpp.org/std/the-standard(我使用了 2012 年 1 月草案的链接)相关部分是 1.10(第 11 页到第 14 页)。

的定义发生在之前旨在模仿兰波特;分布式系统中的时间、时钟和事件顺序,CACM,21(7):558-565,1978 年 7 月 http://research.microsoft.com/en-us/um/people/lamport/pubs/time-clocks.pdf. C++ acquires对应兰波特的receives, C++ releases对应兰波特的sends。 Lamport 对单个线程内的事件序列进行了总排序,其中 C++ 必须允许部分排序(请参阅第 1.9 节,第 13-15 段,第 10 页,了解 C++ 的定义之前测序.) 尽管如此,之前测序订购几乎是您所期望的。语句按照它们在程序中给出的顺序进行排序。第 1.9 节第 14 段:“与完整表达式相关的每个值计算和副作用都在每个值之前排序 与下一个要评估的完整表达式相关的计算和副作用。”

第 1.10 节的重点是说一个程序无数据竞争产生相同的明确定义的值,就像程序在具有顺序一致内存且没有编译器重新排序的机器上运行一样。如果存在数据竞争,则程序根本没有定义的语义。如果不存在数据竞争,则允许编译器(或机器)对不会造成顺序一致性假象的操作进行重新排序。

第 1.10 节第 21 段(第 14 页)说: 程序不是无数据竞争如果存在一对来自不同线程对对象 X 的访问 A 和 B,则这些访问中至少有一个会产生副作用,并且 A 不会发生在 B 之前,B 也不会发生在 A 之前。否则,程序会出现数据争用-自由的。

第 6-20 段给出了happens-before关系的非常仔细的定义。关键定义是第 12 段:

” 评价A 发生在之前评估 B 如果:

  • A 在 B 之前排序,或者
  • A 线程间发生在 B 之前。”

所以如果一个获取是之前测序(在同一线程中)几乎任何其他语句,那么获取必须出现在该语句之前。 (包括该语句是否执行写入。)

同样:如果几乎任何陈述都是之前测序(在同一个线程中)发布,那么该语句必须出现在发布之前。 (包括该语句是否只进行值计算(读取)。)

编译器的原因is允许将其他计算从释放之后移动到释放之前(或从获取之前移动到获取之后)是因为这些操作专门执行not在关系之前发生线程间(因为它们位于临界区之外)。如果它们竞争,则语义是未定义的,如果它们不竞争(因为它们不共享),那么您无法准确判断它们何时发生同步。

这是一个很长的说法:cppreference.com 对获取和释放的定义是完全错误的。您的示例程序没有数据竞争条件,并且不会发生 PANIC。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++11内存_顺序_获取和内存_顺序_释放语义? 的相关文章

随机推荐