每个 memory_order 是什么意思? [英] What do each memory_order mean?

查看:37
本文介绍了每个 memory_order 是什么意思?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我读了一章,但不太喜欢.我仍然不清楚每个内存顺序之间的区别是什么.这是我目前的推测,在阅读了更简单的 http://en.cppreference.com/w/cpp/atomic/memory_order

以下内容有误,请勿学习

  • memory_order_relaxed:不同步,但在不同原子变量中从另一种模式完成订单时不会被忽略
  • memory_order_consume:同步读取这个原子变量,但是它不同步在此之前写入的宽松变量.但是,如果线程在修改 Y(并释放它)时使用 var X.其他消耗 Y 的线程也会看到 X 被释放?我不知道这是否意味着这个线程推出了 x(显然是 y)的变化
  • memory_order_acquire:同步读取这个原子变量并确保在此之前写入的宽松变量也同步.(这是否意味着所有线程上的所有原子变量都已同步?)
  • memory_order_release:将原子存储推送到其他线程(但前提是它们使用消耗/获取读取变量)
  • memory_order_acq_rel:用于读/写操作.执行获取以便您不会修改旧值并发布更改.
  • memory_order_seq_cst:与获取释放相同,除了它强制在其他线程中看到更新(如果 a 存储在另一个线程上.我存储 b 与seq_cst.第三个线程使用relax 读取a 将看到变化以及b 和任何其他原子变量?).

我想我明白了,但如果我错了,请纠正我.我找不到任何用易于阅读的英语解释它的内容.

解决方案

GCC Wiki 提供了一个 非常透彻且易于理解的解释带有代码示例.

(编辑了摘录,并添加了重点)

重要提示:

在将我自己的措辞添加到答案的过程中重新阅读从 GCC Wiki 复制的以下引用后,我注意到该引用实际上是错误的.他们以错误的方式获取消费.release-consume 操作仅提供对依赖数据的排序保证,而 release-acquire 操作提供这种保证,而不管数据是否依赖于原子值.><块引用>

第一个模型是顺序一致的".这是当没有指定时使用的默认模式,它是最严格的.它也可以通过 memory_order_seq_cst 显式指定.它提供相同的限制和限制来移动负载,顺序程序员天生就熟悉,除了它适用于跨线程.
[...]
从实践的角度来看,这相当于所有原子操作都充当了优化障碍.在原子操作之间重新排序是可以的,但不能跨操作重新排序.线程本地内容也不受影响,因为其他线程不可见.[...] 这种模式还提供了跨所有线程的一致性.

相反的方法memory_order_relaxed.该模型通过消除发生之前的限制,允许更少的同步.这些类型的原子操作也可以对它们执行各种优化,例如删除死存储和共享.[...] 没有任何发生在边缘之前,任何线程都不能依赖另一个线程的特定顺序.
当程序员只是希望一个变量本质上是原子的,而不是使用它来同步其他共享内存数据的线程时,最常使用放松模式.

第三种模式(memory_order_acquire/memory_order_release)是其他两种模式之间的混合.获取/释放模式类似于顺序一致模式,不同之处在于它仅对因变量应用发生在之前的关系.这允许放宽独立写入的独立读取之间所需的同步.

memory_order_consume 是对释放/获取内存模型的进一步细微改进,它通过在对非相关共享变量进行排序之前删除发生的事件而略微放宽了要求.
[...]
真正的区别归结为硬件必须刷新多少状态才能同步.由于消费操作可能因此执行得更快,知道自己在做什么的人可以将其用于性能关键应用程序.

以下是我自己尝试更平凡的解释:

另一种看待它的方法是从重新排序读取和写入(原子和普通)的角度来看问题:

所有原子操作都保证在它们自己内部是原子的(两个原子操作的组合作为一个整体不是原子的!)并且在整个顺序中可见它们出现在执行流的时间线上.这意味着在任何情况下都不能对原子操作进行重新排序,但其他内存操作很可能会重新排序.编译器(和 CPU)通常会进行诸如优化之类的重新排序.
这也意味着编译器必须使用任何必要的指令来保证在任何时间执行的原子操作将看到每个其他原子操作的结果,可能在另一个处理器内核上(但不一定是其他操作),这些操作是之前执行的.

现在,放松就是最起码的.它不做任何额外的事情,也不提供其他保证.这是最便宜的操作.对于强有序处理器架构(例如 x86/amd64)上的非读-修改-写操作,这归结为一个普通的普通移动.

顺序一致操作正好相反,它不仅对原子操作执行严格的排序,而且对发生在之前或之后的其他内存操作也执行严格的排序.任何人都无法跨越原子操作强加的障碍.实际上,这意味着失去优化机会,并且可能必须插入栅栏指令.这是最贵的型号.

释放操作可以防止普通的加载和存储在原子操作之后被重新排序,而获取操作可以阻止普通的加载和存储在原子操作之前重新排序.其他一切仍然可以移动.
防止在相应的原子操作之后移动存储和在相应原子操作之前移动负载的组合确保获取线程看到的任何内容都是一致的,只损失少量优化机会.
人们可能会将其视为一种不存在的锁,它被(作者)释放并(被读者)获取.除了……没有锁.

在实践中,发布/获取通常意味着编译器不需要使用任何特别昂贵的特殊指令,但它不能根据自己的喜好自由地重新排序加载和存储,这可能会错过一些(小)优化机会.

最后,consumeacquire 是相同的操作,只是排序保证仅适用于相关数据.相关数据将例如是由原子修改指针指向的数据.
可以说,这可能会提供一些在获取操作中不存在的优化机会(因为较少的数据受到限制),但是这是以更复杂和更容易出错的代码以及非平凡任务为代价的使依赖链正确.

目前不鼓励在修订规范时使用 consume 排序.

I read a chapter and I didn't like it much. I'm still unclear what the differences is between each memory order. This is my current speculation which I understood after reading the much more simple http://en.cppreference.com/w/cpp/atomic/memory_order

The below is wrong so don't try to learn from it

  • memory_order_relaxed: Does not sync but is not ignored when order is done from another mode in a different atomic var
  • memory_order_consume: Syncs reading this atomic variable however It doesnt sync relaxed vars written before this. However if the thread uses var X when modifying Y (and releases it). Other threads consuming Y will see X released as well? I don't know if this means this thread pushes out changes of x (and obviously y)
  • memory_order_acquire: Syncs reading this atomic variable AND makes sure relaxed vars written before this are synced as well. (does this mean all atomic variables on all threads are synced?)
  • memory_order_release: Pushes the atomic store to other threads (but only if they read the var with consume/acquire)
  • memory_order_acq_rel: For read/write ops. Does an acquire so you don't modify an old value and releases the changes.
  • memory_order_seq_cst: The same thing as acquire release except it forces the updates to be seen in other threads (if a store with relaxed on another thread. I store b with seq_cst. A 3rd thread reading a with relax will see changes along with b and any other atomic variable?).

I think I understood but correct me if i am wrong. I couldn't find anything that explains it in easy to read english.

解决方案

The GCC Wiki gives a very thorough and easy to understand explanation with code examples.

(excerpt edited, and emphasis added)

IMPORTANT:

Upon re-reading the below quote copied from the GCC Wiki in the process of adding my own wording to the answer, I noticed that the quote is actually wrong. They got acquire and consume exactly the wrong way around. A release-consume operation only provides an ordering guarantee on dependent data whereas a release-acquire operation provides that guarantee regardless of data being dependent on the atomic value or not.

The first model is "sequentially consistent". This is the default mode used when none is specified, and it is the most restrictive. It can also be explicitly specified via memory_order_seq_cst. It provides the same restrictions and limitation to moving loads around that sequential programmers are inherently familiar with, except it applies across threads.
[...]
From a practical point of view, this amounts to all atomic operations acting as optimization barriers. It's OK to re-order things between atomic operations, but not across the operation. Thread local stuff is also unaffected since there is no visibility to other threads. [...] This mode also provides consistency across all threads.

The opposite approach is memory_order_relaxed. This model allows for much less synchronization by removing the happens-before restrictions. These types of atomic operations can also have various optimizations performed on them, such as dead store removal and commoning. [...] Without any happens-before edges, no thread can count on a specific ordering from another thread.
The relaxed mode is most commonly used when the programmer simply wants a variable to be atomic in nature rather than using it to synchronize threads for other shared memory data.

The third mode (memory_order_acquire / memory_order_release) is a hybrid between the other two. The acquire/release mode is similar to the sequentially consistent mode, except it only applies a happens-before relationship to dependent variables. This allows for a relaxing of the synchronization required between independent reads of independent writes.

memory_order_consume is a further subtle refinement in the release/acquire memory model that relaxes the requirements slightly by removing the happens before ordering on non-dependent shared variables as well.
[...]
The real difference boils down to how much state the hardware has to flush in order to synchronize. Since a consume operation may therefore execute faster, someone who knows what they are doing can use it for performance critical applications.

Here follows my own attempt at a more mundane explanation:

A different approach to look at it is to look at the problem from the point of view of reordering reads and writes, both atomic and ordinary:

All atomic operations are guaranteed to be atomic within themselves (the combination of two atomic operations is not atomic as a whole!) and to be visible in the total order in which they appear on the timeline of the execution stream. That means no atomic operation can, under any circumstances, be reordered, but other memory operations might very well be. Compilers (and CPUs) routinely do such reordering as an optimization.
It also means the compiler must use whatever instructions are necessary to guarantee that an atomic operation executing at any time will see the results of each and every other atomic operation, possibly on another processor core (but not necessarily other operations), that were executed before.

Now, a relaxed is just that, the bare minimum. It does nothing in addition and provides no other guarantees. It is the cheapest possible operation. For non-read-modify-write operations on strongly ordered processor architectures (e.g. x86/amd64) this boils down to a plain normal, ordinary move.

The sequentially consistent operation is the exact opposite, it enforces strict ordering not only for atomic operations, but also for other memory operations that happen before or after. Neither one can cross the barrier imposed by the atomic operation. Practically, this means lost optimization opportunities, and possibly fence instructions may have to be inserted. This is the most expensive model.

A release operation prevents ordinary loads and stores from being reordered after the atomic operation, whereas an acquire operation prevents ordinary loads and stores from being reordered before the atomic operation. Everything else can still be moved around.
The combination of preventing stores being moved after, and loads being moved before the respective atomic operation makes sure that whatever the acquiring thread gets to see is consistent, with only a small amount of optimization opportunity lost.
One may think of that as something like a non-existent lock that is being released (by the writer) and acquired (by the reader). Except... there is no lock.

In practice, release/acquire usually means the compiler needs not use any particularly expensive special instructions, but it cannot freely reorder loads and stores to its liking, which may miss out some (small) optimization opportuntities.

Finally, consume is the same operation as acquire, only with the exception that the ordering guarantees only apply to dependent data. Dependent data would e.g. be data that is pointed-to by an atomically modified pointer.
Arguably, that may provide for a couple of optimization opportunities that are not present with acquire operations (since fewer data is subject to restrictions), however this happens at the expense of more complex and more error-prone code, and the non-trivial task of getting dependency chains correct.

It is currently discouraged to use consume ordering while the specification is being revised.

这篇关于每个 memory_order 是什么意思?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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