为了防止部分读取原子存储,必须使用“ memory_order_relaxed”

问题描述 投票:0回答:1

假设线程1正在使用v(或任何其他顺序)对变量memory_order_release进行原子存储,线程2正在使用vmemory_order_relaxed进行原子读取。

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

  1. 如果现在线程2在不使用原子操作的情况下读取v,理论上我们可以进行部分读取吗?
  2. 我们可以在实践中进行部分阅读吗? (询问,因为我认为这在大多数处理器上都无关紧要,但我不确定。)
c++ stdatomic
1个回答
1
投票

对于1.您打算怎么做?

atomic<T> v是使T()隐式转换过载的模板,类似于.load(mo_seq_cst)。这使得撕裂成为不可能。 seq_cst原子就像轻松加上一些排序保证。

模板还使诸如++之类的运算符过载以执行原子.fetch_add(1, mo_seq_cst)。 (或者对于预递增,使用1 + fetch_add来产生已经递增的值)。


当然,如果您通过用非原子atomic<T>读取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。编译器do

使用它们来优化对齐的8字节负载,即使不是原子性也不是目标,因此您可能会“很幸运”并且无论如何都不会撕裂。

小对象通常无论如何都是原子的;参见Why is integer assignment on a naturally aligned variable atomic on x86?


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

© www.soinside.com 2019 - 2024. All rights reserved.