我对x86上的顺序一致性加载操作很感兴趣。
就我从编译器生成的汇编清单中所看到的,它被实现为在x86上的普通加载,然而据我所知,普通加载保证具有获取语义,而普通存储则保证具有发布语义。
顺序一致性存储是通过锁定xchg来实现的,而加载则是普通加载。这对我来说听起来很奇怪,请您详细解释一下?
添加
刚在互联网上发现,只要使用锁定的xchg进行存储,顺序一致性原子加载就可以简单地完成mov,但没有证明和文档链接。
我对x86上的顺序一致性加载操作很感兴趣。
就我从编译器生成的汇编清单中所看到的,它被实现为在x86上的普通加载,然而据我所知,普通加载保证具有获取语义,而普通存储则保证具有发布语义。
顺序一致性存储是通过锁定xchg来实现的,而加载则是普通加载。这对我来说听起来很奇怪,请您详细解释一下?
添加
刚在互联网上发现,只要使用锁定的xchg进行存储,顺序一致性原子加载就可以简单地完成mov,但没有证明和文档链接。
在x86架构中,如果使用了“LOCK”指令进行SC存储,数值被正确对齐,并且使用了“normal”WB缓存模式,则普通的MOV指令足以实现原子顺序一致性加载。
完整的映射请参见我的博客文章:http://www.justsoftwaresolutions.co.uk/threading/intel-memory-ordering-and-c++-memory-model.html,有关允许排序的详细信息,请参阅Intel处理器文档:http://developer.intel.com/products/processor/manuals/index.htm。
如果您使用“WC”缓存模式或“non-temporal”指令(如MOVNTI),那么所有情况都无法保证,因为处理器不一定会及时将数据写回主内存。
如果对齐,x86上的读取本质上是原子性的,因此在英特尔汇编手册vol 2A中的MOV
指令下的部分以及LOCK
前缀应该提到这一点。其他卷也可能提到这一点。
然而,如果您想要进行原子读取
,可以使用_InterlockedExchangeAdd((LONG*)&var,0)
(又称LOCK XADD
),这将产生旧值,但不会更改其值。同样可以使用InterlockCompareExchange((LONG*)&var,var,var)
(又称LOCK CMPXCHG
),但我认为没有必要这样做。
CAS(&var, 0,0)
永远不会修改该值,并避免首先对共享变量进行非原子加载。 - Peter Cordes在多处理器环境中,寄存器到内存的传输和反向传输不一定是原子性的。
阅读
XOR EAX, EAX
LOCK XADD [address], EAX
这条第一条指令将清零EAX寄存器,第二条指令将交换EAX和[address]的内容,并再次将两者之和存储在[address]中。由于EAX寄存器在之前被清零了,所以没有任何变化。
编写
XCHG [address], EAX
EAX寄存器将获取要存储到指定地址的值。
编辑:使用LOCK ADD EAX,[address]会导致“无效操作码异常”,因为目标操作数不是内存地址。
当LOCK前缀与任何其他指令一起使用或未向内存进行写操作时,将生成无效操作码异常(#UD)。8.1.2.2软件控制总线锁定
编辑2:总结评论中的信息。
虽然
"[...]处理器的锁定协议在交换操作的持续时间内自动实现,无论是否有LOCK前缀或IOPL的值的存在或不存在"
但存在限制
“跨总线宽度、高速缓存行和页面边界分割的可缓存内存访问不能保证是原子的”
lock
前缀的情况下,如mov eax,[mem]
(对于对齐的mem是保证原子性的,这就是为什么编译器使用alignof( atomic<T> ) = sizeof(T)
用于无锁原子操作。请参见为什么x86上自然对齐变量的整数赋值是原子的?)。 - Peter Cordeslock
操作来进行原子RMW,或者用于加载或存储的seq-cst排序。你可以选择其中之一;通常的选择是廉价的加载。请参见https://www.cl.cam.ac.uk/~pes20/cpp/cpp0xmappings.html - Peter Cordeslock
前缀。但是分裂锁的性能是灾难性的。如果你坚持支持alignf(atomic<int>) = 1
,那么最好使用自旋锁,即回退到不lock_free
模式的操作。大多数系统都不会这样做;alignof(atomic<T>) = sizeof(T)
和未对齐对象UB可能导致撕裂。此外,如果你依赖于未对齐的原子操作,你的程序将无法在非x86系统上移植;大多数LL/SC机器没有任何东西可以允许未对齐的原子操作。 - Peter Cordes