CAS指令如何保证原子性
Posted
技术标签:
【中文标题】CAS指令如何保证原子性【英文标题】:How does CAS instructions guarantee atomicity 【发布时间】:2016-08-14 08:53:01 【问题描述】:根据Wiki,CAS 做了这样的事情:
function cas(p : pointer to int, old : int, new : int) returns bool
if *p ≠ old
return false
*p ← new
return true
好吧,在我看来,如果多个处理器尝试使用相同的参数执行 CAS 指令,那么可能会同时进行多次写入尝试,因此无论如何这样做都不安全。
我哪里错了?
【问题讨论】:
同时进行多次写入不是问题,只要清楚谁赢了。您可能会想到ABA problem,它需要额外的处理才能安全。但对于整数来说,这没问题。 @Voo "同时进行多次写入不是问题" -- 你确定吗?我认为这样做是不安全的,因为例如,x86 不能保证非对齐 DWORD 的写入原子性 【参考方案1】:同时来自多个内核的原子读-比较-写指令确实相互竞争,但这取决于硬件来解决。 Hardware arbitration of atomic RMW instructions 在现代 CPU 中是真实存在的,并且提供了一定程度的公平性,因此在 lock cmpxchg
上旋转的一个线程不能完全阻止其他线程做同样的事情。 (虽然这是一个糟糕的设计:最好在获取负载上旋转,并且只有在它应该成功时才执行 CAS)
无法保证它们发生的顺序,这就是为什么您需要仔细设计算法,以便正确性仅取决于比较和交换是原子的。 (ABA problem 是一个常见的陷阱)。
顺便说一句,整个伪代码块作为单个原子操作发生。对于硬件而言,将读取-比较-写入或读取-修改-写入作为单个原子操作发生比仅仅存储要困难得多,MESIF/MOESI 处理得很好。
你确定吗?我认为这样做是不安全的,因为例如,x86 不能保证非对齐 DWORD 的写入原子性
lock cmpxchg
使操作原子化,而不管对齐方式。对于未对齐的情况,它可能会慢很多,尤其是在以原子方式修改单个缓存行还不够的缓存行拆分上。
另请参阅Atomicity on x86,我在这里解释了原子操作的含义。
【讨论】:
只有CMPXCHG
指令还不够吗?我也应该用LOCK
前缀标记它吗?
@FrozenHeart:如果你希望它是原子的,你需要lock
! lock
对于带有内存操作数的 xchg
是隐含的,但对于任何其他指令都不是。
所以,CAS
本身不足以提供原子性,所以我们也应该在它前面加上 LOCK
前缀。那么像CMPXCHG
这样的指令有什么意义呢?就因为我们当时只能LOCK
一条指令?
使用诸如cmpxchg
等不带前缀的指令至少在以下几个场景中很有用:(1) 在单 CPU 系统上,可以删除锁定前缀,至少在晦涩的场景之外例如 DMA 或其他非 CPU 代理在同一个宏上竞争。单 CPU 构建(即 CONFIG_SMP=n)就是一个很好的例子 - 大多数原子操作都隐藏在宏后面,当 SMP 被禁用时,可以简单地省略 lock
前缀。如果没有 lock
前缀,面对中断,您仍然拥有指令原子性,这就是您所需要的。
@BeeOnRope:这很好。 Linux 实际上比这更进一步:在 UP 系统上引导 SMP 内核实际上会将 lock
前缀修补到所有 UP 不需要的地方的 0x90
NOP 中。如果禁用抢占,它还会修补一些锁定/解锁代码。 (IIRC,内核日志消息是patching SMP alternatives
或其他东西)。关于在指令级别上原子化的优点;我可以看到非锁定的 CMPXCHG 实际上是如何有用的,就像inc [foo]
is different from separate load/store 一样。【参考方案2】:
如果您阅读 wiki,它会说 CAS“是您发布的代码的以下伪代码的原子版本”。原子意味着代码将在不受其他线程中断的情况下执行。因此,即使多个线程尝试使用相同的参数(如您所建议的那样)同时执行此代码,也只有其中一个会返回 true,因为实际上它们不会同时执行,因为原子性要求它们单独运行。
而且由于您提到“x86 不保证非对齐 DWORD 的写入原子性”,因此这里也不是问题,因为 cas 函数的原子属性。
【讨论】:
以上是关于CAS指令如何保证原子性的主要内容,如果未能解决你的问题,请参考以下文章