C ++ 11 memory_order_acquire和memory_order_release语义?

时间:2013-04-23 22:02:09

标签: c++ multithreading c++11 memory-model

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

  • 获取操作:当前线程中的读取可以在此加载之前重新排序。
  • 发布操作:当前线程中没有写入可以在此商店之后重新排序。

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

相同的在线资源(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?我错过了什么?

1 个答案:

答案 0 :(得分:20)

spinlock mutex实现对我来说没问题。我认为他们完全错误地定义了获取发布

以下是我所知道的获取/发布一致性模型的最清晰解释:Gharachorloo; Lenoski; Laudon; Gibbons; Gupta; Hennessy: Memory consistency and event ordering in scalable shared-memory multiprocessors, Int'l Symp Comp Arch, ISCA(17):15-26, 1990, doi 10.1145/325096.325102。 (doi是ACM付费墙的后面。实际链接是在付费墙后面的而不是的副本。)

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

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

关键在于:获取和发布是顺序一致的(所有线程全局同意获取和发布的顺序。)所有线程全局同意在特定线程的获取和发布之间发生的事情发生获得和释放之间。但允许在发布之后移动(或通过硬件或编译器)正常加载和存储发布后的正常加载和存储,并且允许在获取之前正常加载和存储在获取之后移动(通过硬件或编译器)。

C++ standard(我使用2012年1月草案的链接)中,相关部分为1.10(第11至14页)。

之前的定义之前是Lamport; Time, Clocks, and the Ordering of Events in a Distributed System, CACM, 21(7):558-565, Jul 1978之后的模型。 C ++ acquire 对应Lamport的 receive ,C ++ release 对应Lamport的发送。 Lamport在单个线程中对事件序列下了一个总顺序,其中C ++必须允许部分顺序(参见第10节,第13-15段,第10页,了解序列的C ++定义 - 之前 。)仍然,排序前排序几乎是你所期望的。语句按照程序中给出的顺序排序。第1.9节第14段:“每个值计算和与全表达相关的副作用在每个值之前排序 计算和与下一个要评估的完整表达式相关的副作用。“

1.10节的要点是说 data-race-free 的程序产生的定义相同,就像程序在具有顺序一致内存的机器上运行一样没有编译器重新排序。如果存在数据竞争,则程序根本没有定义的语义。如果没有数据竞争,则允许编译器(或机器)重新排序不会导致顺序一致性错觉的操作。

第1.10节第21段(第14页)说:如果存在从不同线程到对象X的一对A和B访问,则程序不是无数据竞争,至少一个这些访问中有一个副作用,在A之前没有A发生,在A之前也没有发生B.否则程序是无数据竞争的。

第6-20段对发生在之前的关系给出了非常仔细的定义。关键定义是第12段:

“评估 A在评估B之前发生,如果:

  • A在B之前排序,或
  • 线程发生在B之前。“

因此,如果获取在之前(在同一个线程中)排序几乎任何其他语句,则获取必须在该语句之前发生。 (包括该语句是否执行写入。)

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

编译器 允许将其他计算从发布之后移动到发布之前(或从获取之前到获取之后)的原因是因为这些操作特别是 not 在关系之前发生了一个内部线程(因为它们在临界区之外)。如果它们竞争,语义是未定义的,如果它们没有竞争(因为它们没有被共享),那么你无法确切地知道它们何时发生在同步方面。

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