如果仅写入值,我是否需要原子?

Dan*_*tel 4 c c++ assembly x86-64 stdatomic

假设我有多个线程访问相同的内存位置。而且,如果有的话,它们都写入相同的值,但没有人读取它。之后,所有线程(通过锁)收敛,然后我才读取值。我需要为此使用原子吗?这是针对 x86_64 系统的。该值是一个 int32。

And*_*zel 10

根据官方 ISO C 标准的 §5.1.2.4 ¶25 和 ¶4,两个不同的线程使用非原子操作以无序方式写入同一内​​存位置会导致未定义行为。如果所有线程都写入相同的值,则 ISO C 标准也不例外。

尽管 x86/x64 CPU 的 Intel/AMD 规范保证将 32 位整数写入 4 字节对齐的地址是原子的,但 ISO C 标准不保证这样的操作是原子的,除非您使用由 ISO C 标准保证为原子的数据类型(例如atomic_int_least32_t)。因此,即使您的线程将 type 值写入int32_t4 字节对齐的地址,根据 ISO C 标准,您的程序仍然会导致未定义的行为。

然而,出于实际目的,假设编译器正在生成以原子方式执行操作的汇编指令可能是安全的,前提是满足对齐要求。

即使内存写入未对齐并且 CPU 不会以原子方式执行写入指令,您的程序仍可能按预期工作。将一个写操作拆分为两个写操作应该无关紧要,因为所有线程都在写入完全相同的值。

如果您决定不使用原子变量,那么您至少应该将该变量声明为volatile. 否则,编译器可能会发出汇编指令,导致变量仅存储在 CPU 寄存器中,因此其他 CPU 可能永远不会看到对该变量的任何更改。

因此,要回答您的问题:可能没有必要将您的变量声明为原子变量。但是,它仍然强烈推荐。通常,对多个线程访问的变量的所有操作都应该是原子的或受互斥锁保护。此规则的唯一例外是所有线程都对该变量执行只读操作。

玩弄未定义的行为可能很危险,通常不推荐。特别是,如果编译器检测到导致未定义行为的代码,则允许将该代码视为无法访问并对其进行优化。在某些情况下,一些编译器实际上会这样做。有关详细信息,请参阅Microsoft 博主 Raymond Chen 撰写的这篇非常有趣的帖子

此外,请注意写入同一位置(甚至同一缓存线)的多个线程可能会中断CPU 管道,因为 x86/x64 架构保证了必须强制执行的强内存排序。如果 CPU 的高速缓存一致性协议检测到由于另一个 CPU 写入同一高速缓存行而可能违反了内存顺序,则可能必须清除整个 CPU 管道。出于这个原因,所有线程写入不同的内存位置(在不同的缓存行中,至少相隔 64 字节)并在所有线程同步后分析写入的数据可能更有效。

  • *可以安全地假设编译器正在生成以原子方式执行操作的汇编指令* - 是的,但假设存储或加载发生*根本*是不安全的。它们可以被下沉或提升出循环,除非您使用“易失性”滚动自己的原子 (4认同)
  • @HadiBrais:您假设两个写入相同值的线程不会发生冲突。但该标准确实更早地更严格地定义了术语“冲突”:同一节的 p4: *如果两个表达式计算其中一个修改内存位置,而另一个读取或修改同一内存位置,则两个表达式计算会发生冲突。* 没有例外用于写入相同的值;从技术上讲,这是数据竞赛 UB。 (3认同)
  • @HadiBrais:我并不怀疑它实际上可以在真正的编译器上工作,并且在读取之前进行同步。只是它是 ISO C++ 中的 UB,因此 x86 的 DeathStation 9000 编译器可以执行 `mov [x], 41` / `inc [x]` 代码生成,如果它愿意的话(发明了从 x 读取的内容),*因为* 这只会破坏数据争用 UB 的代码。是的,正如您所说,如果不同步部分编译为纯写入,则不需要原子性。(但是无论如何你都会有原子性,因为真正的编译器使用 `alignof(int)=4`。) (3认同)
  • 即使在具有弱有序内存模型的 CPU 上,错误共享也是一个问题。它们仍然需要保持一致性,因此存储无法从存储缓冲区提交到 L1d 缓存,除非该行由该核心独占。(在 MESI Exclusive 或 Modified 状态下。)但是,在 x86 上,错误共享也会对内存顺序错误推测管道核武器的读者产生额外的不良影响。这本身并不是“停滞”;如果没有推测性的早期加载,x86 CPU 必须“实际上”按程序顺序进行加载,并在每次缓存未命中加载时停止。 (2认同)

归档时间:

查看次数:

386 次

最近记录:

5 年,8 月 前