哪个std :: sync :: atomic :: Ordering使用?

时间:2015-05-22 23:08:23

标签: std rust sync atomic

std::sync::atomic::AtomicBool的所有方法都采用了内存排序(Relaxed,Release,Acquire,AcqRel和SeqCst),这是我以前没用过的。在什么情况下应该使用这些值?该文档使用令人困惑的“加载”和“存储”术语,我并不理解。例如:

生产者线程改变Mutex持有的某个状态,然后调用AtomicBool :: compare_and_swap(false, true, ordering)(以合并失效),如果交换,则发布“invalidate”消息并发队列(例如mpsc或winapi PostMessage)。消费者线程重置AtomicBool,从队列中读取,并读取互斥锁持有的状态。生产者是否可以使用轻松排序,因为它之前是互斥锁,或者必须使用Release?消费者可以使用store(false, Relaxed),还是必须使用compare_and_swap(true, false, Acquire)来接收来自互斥锁的更改?

如果制作人和消费者分享RefCell而不是Mutex

,会怎样?

1 个答案:

答案 0 :(得分:11)

我不是这方面的专家,而且它非常复杂,所以请随时批评我的帖子。正如mdh.heydari所指出的,cppreference.com有much better documentation of orderings而不是Rust(C ++有一个几乎相同的API)。

对于您的问题

您需要使用"发布"在您的制作人中订购并且"获得"在您的消费者中订购。这可确保在AtomicBool设置为true之前发生数据突变。

如果您的队列是异步的,那么消费者将需要继续尝试在循环中读取它,因为生成器可能在设置AtomicBool和将某些内容放入队列之间中断。

如果生产者代码可能在客户端运行之前运行多次,那么您就不能使用RefCell,因为它们可能会在客户端读取数据时改变数据。没关系,没关系。

还有其他更好,更简单的方法来实现这种模式,但我想你只是以它为例。

什么是订单?

不同的顺序与另一个线程在发生原子操作时发生的情况有关。编译器和CPU通常都允许重新排序指令以优化代码,并且排序会影响他们允许重新排序指令的程度。

你总是可以使用SeqCst,它基本上保证每个人都会看到该指令已经发生在你把它放在其他指令的任何地方,但在某些情况下,如果你指定一个限制较少的顺序,那么LLVM和CPU可以更好地优化您的代码。

您应该将这些排序视为应用于内存位置(而不是应用于指令)。

订购类型

轻松订购

除了对内存位置进行任何原子修改外,没有任何限制(所以它要么完全发生,要么根本不发生)。如果单个线程检索/设置的值不重要,只要它们是原子的,那么这对于像计数器这样的东西就可以了。

获取订购

此约束表示在"获取"之后,代码中出现的任何变量读取应用后不能重新排序。因此,在您的代码中,您可以读取一些共享内存位置并获取值X,该值T存储在该内存位置T,然后您应用"获取"约束。在应用约束后读取的任何内存位置都将具有它们在T或更晚的时间具有的值。

这可能是大多数人期望直观地发生的事情,但是因为只要他们不改变结果就允许CPU和优化器重新排序指令,所以不能保证。

为了获得"获得"为了有用,它必须与" release"配对,因为否则不能保证另一个线程没有重新排序它应该在时间{{{ 1}}到了更早的时间。

获取 - 读取您正在寻找的标志值意味着您不会在释放存储到标志之前通过写入实际更改的其他地方看到过时的值。

发布订购

此约束表示在" release"之前,代码中出现的任何变量都会写入。应用后不能重新排序。因此,在您的代码中,您可以写入一些共享内存位置,然后在T时设置一些内存位置t,然后应用"发布"约束。在"发布"之前出现在代码中的任何写入保证在它之前发生。

同样,这是大多数人期望直观地发生的事情,但是没有限制就无法保证。

如果试图读取值X的其他线程没有使用"获取",那么就不能保证看到关于其他更改的新值变量值。因此它可以获得新值,但它可能看不到任何其他共享变量的新值。另请注意,测试是 hard 。一些硬件在实践中不会显示使用一些不安全的代码进行重新排序,因此问题可能无法检测到。

Jeff Preshing wrote a nice explanation of acquire and release semantics,请仔细阅读,如果这还不清楚。

AcqRel订购

这样做AcquireRelease排序(即两个限制都适用)。我不确定何时这是必要的 - 如果有一些Release,有些Acquire,有些MFENCE,有些同时执行这两种操作,则可能会有帮助。但我不是真的很确定。

SeqCst Ordering

这是最具限制性的,因此也是最慢的选择。它强制内存访问看起来以与每个线程相同的顺序发生。这需要在x86上对原子变量(完全内存屏障,包括StoreLoad)的所有写入执行lock指令,而较弱的顺序不需要。 (正如你在this C++ compiler output中看到的那样,SeqCst加载并不需要x86上的障碍。)

读取 - 修改 - 写入访问(如原子增量或比较和交换)在x86上使用ldar指令完成,这些指令已经是完全内存屏障。如果您非常关心在非x86目标上编译高效代码,那么尽可能避免使用SeqCst是有意义的,即使对于原子读取 - 修改 - 写入操作也是如此。 There are cases where it's needed,但是。

有关原子语义如何转化为ASM的更多示例,请参阅this larger set of simple functions on C++ atomic variables。我知道这是一个Rust问题,但它应该与C ++具有基本相同的API。 godbolt可以针对x86,ARM,ARM64和PowerPC。有趣的是,ARM64具有加载 - 获取(stlr)和存储 - 释放(AcqRel)指令,因此它并不总是必须使用单独的屏障指令。

顺便说一句,x86 CPU始终是#34;强烈排序"默认情况下,这意味着它们始终表现为至少设置了Relaxed模式。所以对于x86"订购"仅影响LLVM优化器的行为方式。另一方面,ARM的排序很弱。默认情况下设置{{1}},以允许编译器完全自由地重新排序,并且在弱排序的CPU上不需要额外的屏障指令。