C ++ 11 memory_order_acquire和memory_order_release语义? [英] C++11 memory_order_acquire and memory_order_release semantics?

查看:231
本文介绍了C ++ 11 memory_order_acquire和memory_order_release语义?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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);

有了这个lock / unlock的定义,如果memory_order_acquire / release (即不禁止重新排序后获取写入):

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

Thread2:
0;
(6)unlock

可能执行以下执行: )x = 1,(5)x = 0,(2)PANIC?我错过了什么?

解决方案

自旋锁互斥实现看起来不错。我认为他们的定义 完全错了。



这是最清楚的解释获取/我知道的发布一致性模型: Gharachorloo; Lenoski;劳顿;吉本斯古普塔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.1 3.3和附图3:




  • 在允许普通加载或存储访问之前,
    相对于任何其他处理器,
    必须执行所有先前的获取访问,并且在允许发布访问与任何其他处理器之间执行
    时,

  • b必须执行加载和存储访问,并且

  • 特殊访问与彼此之间的尊重
    [顺序]一致。



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



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



em>发生之前旨在建模后 Lamport ;时间,时钟和分布式系统中事件的排序 CACM ,21(7):558-565,1978年7月。 C ++ 对应于Lamport的发送 Lamport在单个线程中对事件序列放置了一个总订单,其中C ++必须允许部分订单(参见第1.9节,第13-15页,第10页的顺序的C ++定义 。)不过,排序之前的顺序是你期望的。语句按它们在程序中给出的顺序排序。第1.9节第14段:与全表达式相关的每个价值计算和边效应在每个值
计算和与要评估的下一个完整表达式相关的边效应之前被排序。

第1.10节的整个要点是说,一个无数据无竞争的程序产生的定义好的值就好像程序在机器上运行一样与顺序一致的内存,没有编译器重排序。如果存在数据竞争,则程序根本没有定义的语义。如果没有数据竞争,那么允许编译器(或机器)重新排序不会导致连续一致性错觉的操作。



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



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



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




  • A在B之前排序,或

  • 线程之间在B之前发生。



所以如果一个采集在(在同一个线程)之前排序,几乎任何其他语句,那么采集必须出现在该语句之前。



同样,如果几乎任何语句在之前排序(在同一个线程中) ,那么该语句必须显示在发布之前发生。



允许编译器允许从后面移动其他计算的原因在发布之前(或者在获取之前到获取之后)的发布是因为这些操作特别地在关系之前发生了线程之间的事实(因为它们在关键之外部分)。如果他们的种族语义是未定义的,如果他们不比赛(因为他们不共享),那么你不能确切地知道他们发生了什么同步。



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


http://en.cppreference.com/w/cpp/atomic/memory_order, and other C++11 online references, define memory_order_acquire and memory_order_release as:

  • Acquire operation: no reads in the current thread can be reordered before this load.
  • Release operation: no writes in the current thread can be reordered after this store.

This seems to allow post-acquire-writes to be executed before the acquire operation, which seems weird too me (usual acquire/release operation semantics restrict movement of all memory operations).

Same online source (http://en.cppreference.com/w/cpp/atomic/atomic_flag) suggests that a spinlock mutex can be built using C++ atomics and the above mentioned relaxed memory ordering rules:

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

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

With this definition of lock/unlock, wouldn't the simple code below be broken if memory_order_acquire/release are indeed defined this way (i.e., not forbidding reordering of post-acquire-writes):

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

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

Is the following execution possible: (0) lock, (1) x = 1, (5) x = 0, (2) PANIC ? What did I miss?

解决方案

The spinlock mutex implementation looks okay to me. I think they got the definitions of acquire and release completely wrong.

Here is the clearest explanation of acquire/release consistency models that I am aware of: 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. (The doi is behind the ACM paywall. The actual link is to a copy not behind a paywall.)

Look at Condition 3.1 in Section 3.3 and the accompanying Figure 3:

  • before an ordinary load or store access is allowed to perform with respect to any other processor, all previous acquire accesses must be performed, and
  • before a release access is allowed to perform with respect to any other processor, all previous ordinary load and store accesses must be performed, and
  • special accesses are [sequentially] consistent with respect to one another.

The point is this: acquires and releases are sequentially consistent (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.

In the C++ standard (I used the link to the Jan 2012 draft) the relevant section is 1.10 (pages 11 through 14).

The definition of happens-before is intended to be modeled after Lamport; Time, Clocks, and the Ordering of Events in a Distributed System, CACM, 21(7):558-565, Jul 1978. C++ acquires correspond to Lamport's receives, C++ releases correspond to Lamport's sends. Lamport placed a total order on the sequence of events within a single thread, where C++ has to allow a partial order (see Section 1.9, Paragraphs 13-15, page 10 for the C++ definition of sequenced-before.) Still, the sequenced-before ordering is pretty much what you would expect. Statements are sequenced in the order they are given in the program. Section 1.9, paragraph 14: "Every value computation and side effect associated with a full-expression is sequenced before every value computation and side effect associated with the next full-expression to be evaluated."

The whole point of Section 1.10 is to say that a program that is data-race-free produces the same well defined value as if the program were run on a machine with a sequentially consistent memory and no compiler reordering. If there is a data race then the program has no defined semantics at all. If there is no data race then the compiler (or machine) is permitted to reorder operations that don't contribute to the illusion of sequential consistency.

Section 1.10, Paragraph 21 (page 14) says: A program is not data-race-free if there is a pair of accesses A and B from different threads to object X, at least one of those accesses has a side effect, and neither A happens-before B, nor B happens-before A. Otherwise the program is data-race-free.

Paragraphs 6-20 give a very careful definition of the happens-before relation. The key definition is Paragraph 12:

"An evaluation A happens before an evaluation B if:

  • A is sequenced before B, or
  • A inter-thread happens before B."

So if an acquire is sequenced before (in the same thread) pretty much any other statement, then the acquire must appear to happen before that statement. (Including if that statement performs a write.)

Likewise: if pretty much any statement is sequenced before (in the same thread) a release, then that statement must appear to happen before the release. (Including if that statement just does a value computation (read).)

The reason that the compiler is allowed to move other computations from after a release to before a release (or from before an acquire to after an acquire) is because of the fact that those operations specifically do not have an inter-thread happens before relationship (because they are outside the critical section). If they race the semantics are undefined, and if they don't race (because they aren't shared) then you can't tell exactly when they happened with regard to the synchronization.

Which is a very long way of saying: cppreference.com's definitions of acquire and release are dead wrong. Your example program has no data race condition, and PANIC can not occur.

这篇关于C ++ 11 memory_order_acquire和memory_order_release语义?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆