假设线程1正在使用v
(或任何其他顺序)对变量memory_order_release
进行原子存储,线程2正在使用v
对memory_order_relaxed
进行原子读取。
在这种情况下,应该不可能进行部分读取。部分读取的一个示例是从最新值读取v
的前一半,从旧值读取v
的后一半。
v
,理论上我们可以进行部分读取吗?对于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
小对象通常无论如何都是原子的;参见Why is integer assignment on a naturally aligned variable atomic on x86?
当然,非原子访问不会假定该值可以异步更改,因此循环可以无限期地使用陈旧的值。与宽松的原子不同,在当前的编译器上,它像volatile
一样,它总是重新访问内存。 (当然,通过一致的硬件缓存,只是不将值保留在寄存器中。)