哪个std :: sync :: atomic ::订单使用?

std::sync::atomic::AtomicBool的所有方法都采用了我以前没有使用的内存命令(Relaxed,Release,Acquire,AcqRel和SeqCst)。在什么情况下应该使用这些价值观?该文档使用了我不太明白的混乱的“加载”和“存储”术语。例如:

生产者线程会突变一个由Mutex持有的状态,然后调用AtomicBool :: compare_and_swap(false,true,ordering)(合并无效),如果交换,则将“invalidate”消息发布到并发队列(例如mpsc或winapi PostMessage)。消费者线程重置AtomicBool,从队列中读取,并读取Mutex持有的状态。生产者可以使用轻松的订购,因为它之前是互斥体,还是必须使用Release?消费者可以使用存储(false,Relaxed),还是必须使用compare_and_swap(true,false,Acquire)从互斥体接收更改?

如果生产者和消费者共享一个RefCell而不是互斥体呢?

最佳答案
我不是专家,这真的很复杂,所以请随时批评我的帖子。正如mdh.heydari所指出的那样,cppreference.com有much better documentation of orderings比Rust(C有几乎相同的API)。

对于你的问题

您需要在制造商中使用“发布”订单,并在消费者中“获取”订购。这样可以确保在将AtomicBool设置为true之前发生数据突变。

如果您的队列是异步的,那么消费者将需要继续尝试在循环中读取它,因为生产者可能会在设置AtomicBool并将其放入队列之间中断。

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

还有其他更好和更简单的方法来实现这种模式,但我假设你只是给它作为一个例子。

什么是订购?

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

您可以随时使用SeqCst,这基本上保证每个人都会看到该指令发生在与其他指令相关的地方,但在某些情况下,如果指定的限制性较低,则LLVM和CPU可以更好地优化代码。

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

订购类型

轻松订购

除了对存储器位置进行任何修改之外,没有任何约束是原子的(因此它完全发生或根本不发生)。对于像计数器这样的东西,如果由各个线程设置的值不重要,只要它们是原子,这是很好的。

获取订单

这个约束表示,在应用“获取”之后,在代码中发生的任何变量读取都不能被重新排序。所以,在您的代码中,您会读取一些共享内存位置并获取在时间T存储在该内存位置的值X,然后应用“获取”约束。在应用约束之后,您读取的任何内存位置将具有在时间T或更晚时间内具有的值。

这可能是大多数人期望直观地发生,但是由于CPU和优化器被允许重新排序指令,只要它们不改变结果,就不能保证。

为了“获取”是有用的,它必须与“释放”配对,因为否则不能保证另一个线程没有重新排序应该在时间T发生的写入指令到较早的时间。

获取阅读您要查找的标志值意味着您不会在释放商店之前的写入实际更改的其他地方看到一个过时的值。

发布订购

这个约束说,应用“release”之前在代码中发生的任何变量写入都不能被重新排序。所以,在您的代码中,您写入一些共享内存位置,然后在时间T设置一些内存位置t,然后应用“释放”约束。在“释放”之前,您的代码中出现的任何写入都将保证在其之前发生。

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

如果尝试读取值X的其他线程不使用“获取”,则不能保证相对于其他变量值的更改看到新值。所以它可以获得新的值,但它可能不会看到任何其他共享变量的新值。还要记住,测试很难。一些硬件实际上不会显示重新排序与一些不安全的代码,所以问题可以不被检测。

Jeff Preshing wrote a nice explanation of acquire and release semantics,所以读,如果这不清楚。

AcqRel订购

这同时采用Acquire和Release排序(即两种限制都适用)。我不知道什么时候这是必要的 – 如果有一些发布,一些收购和一些两个,但是我不是很确定,这可能有助于3个或更多的线程的情况。

SeqCst订购

这是最限制性的,因此是最慢的选择。它强制存储器访问看起来以每个线程一个相同的顺序发生。对于原子变量(全存储器障碍,包括StoreLoad)的所有写入,这需要x86上的MFENCE指令,而较弱的排序不需要。 (SeqCst加载不需要在x86上的屏障,正如你可以看到在this C++ compiler output)

读取 – 修改 – 写入访问,如原子增量,或比较和交换,都是在x86上完成的锁定指令,已经是完整的内存障碍。如果您关心编译非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具有负载获取(ldar)和存储释放(stlr)指令,因此并不总是需要使用单独的屏障指令。

顺便说一句,默认情况下,x86 CPU总是“强制排序”,这意味着它们总是像至少设置AcqRel模式一样。所以对于x86“排序”只影响LLVM优化器的行为。另一方面,ARM是弱订购的。轻松设置默认情况下,允许编译器完全自由地重新排序事件,并且不需要在弱排序CPU上的额外的屏障指令。

转载注明原文:哪个std :: sync :: atomic ::订单使用? - 代码日志