C++11 memory_order_acquire and memory_order_release semantic

C++11 memory_order_acquire and memory_order_release semantics?

本文关键字:order memory semantic release acquire C++11 and      更新时间:2023-10-16

http://en.cppreference.com/w/cpp/atomic/memory_order,以及其他c++ 11在线引用,定义memory_order_acquire和memory_order_release为:

  • 获取操作:noreads
  • 释放操作:no

这似乎允许在之前执行获取操作,这看起来也很奇怪(通常的获取/释放操作语义限制所有的移动)内存操作)。

相同的在线资源(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);               

有了lock/unlock的定义,如果memory_order_acquire/release确实是这样定义的(即,不禁止post-acquire-writes的重新排序),下面的简单代码不会被破坏吗?

Thread1:
(0) lock
(1) x = 1;
(2) if (x != 1) PANIC
(3) unlock
Thread2:
(4) lock
(5) x = 0;
(6) unlock
(0) lock, (1) x = 1, (5) x = 0, (2) PANIC ?我错过了什么?

自旋锁互斥锁的实现在我看来还不错。我认为他们对acquirerelease的定义完全错误。

这是我所知道的关于获取/释放一致性模型的最清晰的解释:Lenoski;Laudon;吉本斯;古普塔;[j] . [m] .共享内存多处理器的内存一致性和事件排序,.计算机工程学报,[17]:15-26,1990,doi: 10.1145/325096.325102。(doi在ACM付费墙后面。实际链接是指向的副本,而不是付费墙后面的

请参阅第3.3节中的条件3.1和随附的图3:

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

关键是:获取和释放是顺序一致的1(所有线程全局同意获取和释放发生的顺序)。全局所有线程都同意在特定线程的获取和释放之间发生的事情发生在获取和释放之间。但是在释放之后的正常加载和存储可以被(硬件或编译器)移动到释放之上,而在获取之前的正常加载和存储可以被(硬件或编译器)移动到获取之后。

(脚注1:这对于大多数实现来说是正确的,但是对于一般的ISO c++来说有点夸张了。Reader线程可以不同意其他两个线程完成两个存储的顺序。请参阅4线程的获取/释放语义,关于如何为POWER cpu编译c++的详细答案演示了释放和获取在实践中的区别,而不是seq_cst。但是大多数cpu只通过一致的缓存在内核之间获取数据,这意味着全局顺序确实存在。


在c++标准中(我使用了2012年1月草案的链接),相关部分是1.10(第11页到第14页)。

的定义发生在之前,旨在模仿Lamport;分布式系统中的时间、时钟和事件排序,中国计算机学报,21(7):558-565,1978年7月。c++获取对应Lamport的接收, c++释放对应Lamport的发送。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之前排序,或
  • 线程间发生在B.">

因此,如果一个acquire在(在同一线程中)几乎任何其他语句之前排序,那么该acquire必须出现在该语句之前。(包括该语句是否执行写操作)

同样地:如果几乎任何语句都是顺序在(在同一线程中)释放之前,那么该语句必须出现在释放之前。(包括如果该语句只进行值计算(读))

编译器被允许将其他计算从释放之后移动到释放之前(或从获取之前移动到获取之后)的原因是,这些操作没有具有线程间发生之前的关系(因为它们在临界区之外)。如果它们竞争,语义是未定义的,如果它们不竞争(因为它们不是共享的),那么您就无法准确地知道它们何时发生同步。

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