在iOS上,有两个类似的功能OSAtomicAdd32
和OSAtomicAdd32Barrier
。我想知道你什么时候需要Barrier
变体。
拆解后,它们是:
_OSAtomicAdd32:
ldxr w8, [x1]
add w8, w8, w0
stxr w9, w8, [x1]
cbnz w9, _OSAtomicAdd32
mov x0, x8
ret lr
_OSAtomicAdd32Barrier:
ldaxr w8, [x1]
add w8, w8, w0
stlxr w9, w8, [x1]
cbnz w9, _OSAtomicAdd32Barrier
mov x0, x8
ret lr
在哪种情况下你需要后者的Load-Acquire / Store-Release语义?是否可以重新排序LDXR
/ STXR
条款说明?如果可以,原子更新是否有可能在没有障碍的情况下“丢失”?从我所看到的情况来看,似乎不会发生这种情况,如果是真的,那么为什么你需要Barrier
变体呢?也许只有当你碰巧需要DMB
用于其他目的时?
谢谢!
答案 0 :(得分:12)
哦,弱记忆秩序的令人费解的恐怖......
第一个片段是您的基本原子读取 - 修改 - 写入 - 如果其他人触摸了x1
指向的任何地址,则存储独占将失败并且它将再次尝试直到成功。到现在为止还挺好。但是,这仅适用于独占显示器覆盖的地址(或更正确的区域),因此虽然它对原子性有好处,但对于同步除此之外的其他任何内容都无效值。
考虑CPU1正在等待CPU0将一些数据写入缓冲区的情况。 CPU1坐在那里等待某种同步对象(假设是一个信号量),等待CPU0更新它以表示新数据准备就绪。
现在,第3步发生了什么?也许这一切都按顺序发生。很可能,硬件决定由于没有地址依赖性,它会让信号量的商店超过商店到数据地址。也许信号量存储在缓存中命中而数据没有。也许只是这样做是因为复杂的原因只有那些硬件人才明白。无论哪种方式,CPU1都可以在新数据到达内存之前看到信号量更新,从而读回无效数据。
要解决此问题,CPU0必须在步骤1和步骤2之间设置障碍,以确保在写入信号量之前已经明确写入了数据。让原子写成为屏障是一个很好的简单方法。但是,由于障碍性能会降低性能,因此您需要轻量级无障碍版本以及不需要这种完全同步的情况。
现在,甚至更少直观的部分是CPU1还可以重新排序其负载。由于没有地址依赖性,因此可以在信号量加载之前推测数据加载,而不管CPU0的障碍。因此,CPU1在步骤4和5之间也需要自己的屏障。
对于更权威,但相当重的版本,读取ARM的Barrier Litmus Tests and Cookbook。请注意,这些东西可能令人困惑;)
顺便一提,在这种情况下,获取/发布的架构语义使事情进一步复杂化。因为它们只是单向障碍,而OSAtomicAdd32Barrier
相对于代码之前和之后的代码加起来有一个完整的障碍,它实际上并不保证相对于原子操作本身的任何排序 - 见this discussion from Linux更多解释。当然,这是从建筑的理论角度来看;实际上,A7硬件已经采用“简单”选项将LDAXR
连接到DMB+LDXR
等等,这是不可想象的,这意味着他们可以自行解决这个问题,因为他们处于自由状态编写自己的实现,而不是规范。
答案 1 :(得分:8)
OSAtomicAdd32Barrier()
适用于那些使用OSAtomicAdd()
以外的东西而不仅仅是原子增量的人。具体来说,他们正在基于OSAtomicAdd()
实现自己的多处理同步原语。例如,创建自己的互斥库。 OSAtomicAdd32Barrier()
使用重屏障指令在原子操作的两侧强制执行内存排序。这在正常使用中是不可取的。
总结:
1)如果您只想以线程安全的方式增加整数,请使用OSAtomicAdd32()
2)如果您遇到一堆愚蠢地认为OSAtomicAdd32()
可用作处理器间内存排序和推测障碍的旧代码,请将其替换为OSAtomicAdd32Barrier()
答案 2 :(得分:3)
我猜这只是为这种操作再现现有的与架构无关的语义的一种方式。
使用ldaxr
/ stlxr
对,如果将AtomicAdd32用作同步机制(互斥/信号量),上述序列将确保正确排序 - 无论结果是否为更高级别的操作获得或释放。
所以 - 这不是强制执行原子添加的一致性,而是强制执行获取/释放互斥锁之间的顺序以及对该互斥锁保护的资源执行的任何操作。
它的效率低于您在普通本机同步机制中使用的ldxar
/ stxr
或ldxr
/ stlxr
,但是如果您现有平台无关代码期望用这些语义添加原子,这可能是实现它的最好方法。