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

yon*_*ran 33 sync atomic std rust

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

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

如果生产者和消费者共享一个RefCell而不是一个Mutex

Mic*_*kin 14

我不是这方面的专家,它真的很复杂,所以请随意批评我的帖子.正如mdh.heydari所指出的,cppreference.com有比Rust 更好的排序文档(C++有一个几乎相同的API).


对于你的问题

您需要在生产者中使用"发布"订单并在您的消费者中"获取"订购.这可确保在AtomicBool设置为true 之前发生数据突变.

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

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

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


什么是订单?

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

您可以随时使用SeqCst,这基本上保证每个人都会看到该指令已经发生在相对于其他指令的任何位置,但在某些情况下,如果您指定限制较少的顺序,则LLVM和CPU可以更好地优化您的代码.

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

订购类型

轻松的订购

除了对内存位置进行任何原子修改之外没有任何限制(因此它要么完全发生,要么根本不发生).如果由单个线程检索/设置的值无关紧要,只要它们是原子的,那么对于像计数器这样的东西来说这很好.

获取订购

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

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

为了使"获取"有用,它必须与"释放"配对,因为否则不能保证其他线程没有重新排序应该在T较早时间发生的写入指令.

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

发布订购

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

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

如果尝试读取值的其他线程X不使用"获取",则无法保证在其他变量值的变化中看到新值.因此它可以获得新值,但它可能看不到任何其他共享变量的新值.还要记住测试很难.某些硬件实际上不会显示某些不安全代码的重新排序,因此问题可能无法检测到.

Jeff Preshing写了一个关于获取和释放语义的很好的解释,所以请阅读,如果这不清楚.

AcqRel订购

这既做AcquireRelease订购(即两个限制都适用).我不确定什么时候这是必要的 - 如果有一些Release,一些Acquire,有些同时做三个或更多线程,它可能会有所帮助,但我不太确定.

SeqCst订购

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

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

有关原子语义如何转换为ASM的更多示例,请参阅C++原子变量上的这一大型简单函数集.我知道这是一个Rust问题,但它应该与C++具有基本相同的API.godbolt可以针对x86,ARM,ARM64和PowerPC.有趣的是,ARM64具有load-acquire(ldar)和store-release(stlr)指令,因此它并不总是必须使用单独的屏障指令.


顺便说一句,默认情况下,x86 CPU总是"强烈排序",这意味着它们总是表现得至少AcqRel设置了模式.因此对于x86"排序"仅影响LLVM的优化器行为.另一方面,ARM的排序很弱. Relaxed默认情况下设置,允许编译器完全自由地重新排序,并且不需要在弱排序的CPU上使用额外的屏障指令.

  • 我发现http://preshing.com/20120710/memory-barriers-are-like-source-control-operations/和Preshing的其他文章非常适合围绕这些东西.SeqCst要求CPU甚至阻止StoreLoad重新排序,这需要在x86上使用MFENCE指令.这很贵.它并不是"锁定总线",只是它不能使用在最后一个商店变得全局可见之前读取的预取数据.但是,在x86上,所有读取 - 修改 - 写入操作(如原子增量或比较和交换)也都是完整的内存屏障,所以seqcst是免费提供的.) (2认同)