`memory_order_relaxed` 是防止部分读取原子存储所必需的

is `memory_order_relaxed` necessary to prevent partial reads of atomic stores

假设线程 1 正在使用 memory_order_release(或任何其他顺序)对变量 v 执行原子存储,线程 2 正在使用 [=14= 对 v 执行原子读取].

这种情况下应该不可能有部分读取。部分读取的一个示例是从最新值读取 v 的前半部分,从旧值读取 v 的后半部分。

  1. 如果线程2现在不使用原子操作读取v,理论上我们可以进行部分读取吗?
  2. 实践中可以部分读取吗? (问是因为我认为这对大多数处理器来说应该无关紧要,但我不确定。)

对于 1. 你建议怎么做?

atomic<T> v 是一个模板,它将 T() 隐式转换重载为类似于 .load(mo_seq_cst)。这使得撕裂变得不可能。 seq_cst atomic 就像放松加上一些排序保证。

该模板还重载了像 ++ 这样的运算符来执行原子操作 .fetch_add(1, mo_seq_cst)。 (或者对于预递增,1+fetch_add 以产生已经递增的值)。


当然,如果您通过使用非原子 char*(例如,使用memcpy(&tmp, &v, sizeof(int))如果另一个线程正在修改它,那就是 UB。是的,您在实践中可能会撕裂,具体取决于您的操作方式。

更大的对象更可能是无锁的,但在某些实现上是可能的,例如对于 32 位系统上的 8 字节对象,可以通过特殊指令实现 8 字节原子性,但通常只会使用两个 32 位负载。

例如32 位 x86,其中可以使用 SSE 完成原子 8 字节加载,然后将其反弹回整数 regs。或者 lock cmpxchg8b。当编译器只需要两个整数寄存器时,他们不会这样做。

但是许多提供原子 8 字节加载的 32 位 RISC 具有双寄存器加载,从一条指令产生 2 个输出寄存器。例如ARM ldrd 或 MIPS ld。编译器 使用这些来优化对齐的 8 字节加载,即使原子性不是目标,所以你可能 "get lucky" 并且无论如何都看不到撕裂。

小对象通常碰巧是原子的;见


当然,非原子访问不会假定值可以异步更改,因此循环可以无限期地使用陈旧值。与宽松的原子不同,在当前的编译器上它就像 volatile 一样,因为它总是重新访问内存。 (当然是通过一致的硬件缓存,只是不将值保存在寄存器中。)