gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-11-29 07:40:29 26 4
gpt4 key购买 nike

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

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

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

最佳答案

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

对于您的问题

您需要在生产者中使用“发布”排序并在消费者中使用“获取”排序。这确保了数据突变发生在 AtomicBool 之前。设置为真。

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

如果生产者代码可能在客户端运行之前多次运行,那么您不能使用 RefCell因为他们可以在客户端读取数据时改变数据。否则就好了。

还有其他更好、更简单的方法来实现这个模式,但我假设你只是举个例子。

什么是订单?

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

你可以一直使用 SeqCst ,这基本上保证了每个人都会看到该指令相对于其他指令,无论你把它放在什么地方,但在某些情况下,如果你指定限制较少的顺序,那么 LLVM 和 CPU 可以更好地优化你的代码。

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

订购类型

轻松订购

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

获取订单

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

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

为了使“acquire”有用,它必须与“release”配对,否则不能保证另一个线程没有重新排序它应该在时间T发生的写指令。到更早的时间。

获取读取您正在查找的标志值意味着您不会在其他地方看到一个陈旧的值,而该值实际上在释放存储到标志之前被写入更改。

发布订购

此约束表示在应用“发布”之前发生在您的代码中的任何变量写入都不能重新排序以在其之后发生。所以,在你的代码中,你写了几个共享内存位置,然后在时间 T 设置一些内存位置 t ,然后应用“发布”约束。在应用“发布”之前出现在您的代码中的任何写入都保证在它之前发生。

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

如果另一个线程试图读取值 X不使用“获取”,则不能保证看到与其他变量值变化相关的新值。因此它可以获得新值,但它可能看不到任何其他共享变量的新值。还要记住,测试很难。一些硬件在实践中不会显示一些不安全代码的重新排序,因此问题可能未被发现。

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

AcqRel 排序

这两者都可以 AcquireRelease订购(即两个限制都适用)。我不确定何时需要这样做 - 如果某些 Release 在 3 个或更多线程的情况下可能会有所帮助, 一些 Acquire ,有些人两者都做,但我不太确定。

顺序排序

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

读-修改-写访问,如原子增量或比较和交换,在 x86 上使用 lock 完成。 ed 指令,这些指令已经是满的内存屏障。如果您完全关心在非 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 是弱有序的。 Relaxed是默认设置的,允许编译器完全自由地重新排序,并且在弱排序的 CPU 上不需要额外的屏障指令。

关于std - 使用哪个 std::sync::atomic::Ordering ?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30407121/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com