Multithreading CAS指令如何保证原子性
根据中国科学院的研究,中国科学院的工作如下:Multithreading CAS指令如何保证原子性,multithreading,assembly,atomic,Multithreading,Assembly,Atomic,根据中国科学院的研究,中国科学院的工作如下: function cas(p : pointer to int, old : int, new : int) returns bool { if *p ≠ old { return false } *p ← new return true } 好吧,在我看来,如果几个处理器试图用相同的参数执行CAS指令,那么在同一时间可能会有几次写入尝试,所以无论如何这样做都不安全 我错在哪里?如果你读了维基,它会说
function cas(p : pointer to int, old : int, new : int) returns bool {
if *p ≠ old {
return false
}
*p ← new
return true
}
好吧,在我看来,如果几个处理器试图用相同的参数执行CAS指令,那么在同一时间可能会有几次写入尝试,所以无论如何这样做都不安全
我错在哪里?如果你读了维基,它会说CAS是你发布的代码的“以下伪代码的原子版本”。原子意味着代码将在没有其他线程中断的情况下执行。因此,即使多个线程试图使用相同的参数同时执行此代码(如您所建议的),其中只有一个线程将返回true,因为实际上它们不会同时执行,因为原子性要求它们独立运行
由于您提到“x86不能保证非对齐DWORD写入的原子性”,这也不是一个问题,因为cas函数的原子属性。同时来自多个核的原子读比较写指令确实会相互冲突,但这取决于硬件。在现代CPU中是真实存在的,它提供了一定程度的公平性,因此在
lock cmpxchg
上旋转的一个线程不能完全阻止其他线程执行相同的操作。(尽管这是一个糟糕的设计:最好在获取负载上旋转,并且只有在CAS成功时才进行)
无法保证它们发生的顺序,这就是为什么需要仔细设计算法,以便正确性只取决于比较和交换是原子的。(这是一个常见的陷阱)
顺便说一句,整个伪代码块作为单个原子操作发生。对于硬件来说,将读-比较-写或读-修改-写作为单个原子操作进行要比存储困难得多,而MESIF/MOESI可以很好地处理存储 你确定吗?我认为这样做是不安全的,因为,例如,x86不能保证非对齐DWORD写入的原子性
lock cmpxchg
使操作原子化,而不考虑对齐方式。对于未对齐的缓存线,它可能会慢得多,尤其是在缓存线拆分时,在这种情况下,仅对单个缓存线进行原子化修改是不够的
另请参见我在其中解释的操作原子化的含义。只要清楚谁赢了,同时进行多个写入操作就不是问题。你们可能会想,为了安全起见,需要额外的处理。但是在ints的情况下,这没有问题。@Voo“同时进行多个写入不是问题”——你确定吗?我认为这样做是不安全的,因为,例如,x86不能保证非对齐数据的写入原子性,或者仅仅有
CMPXCHG
指令还不够?我应该用LOCK
前缀来标记它吗?@frozenhart:如果你想让它成为原子的,你需要LOCK
lock
对于带有内存操作数的xchg
是隐式的,但对于任何其他指令都是隐式的。因此,CAS
本身不足以提供原子性,因此我们也应该在它前面加上lock
前缀。那么,像CMPXCHG
这样的指令有什么意义呢?仅仅因为我们一次只能锁定一条指令?使用诸如没有前缀的指令(如cmpxchg
)至少在两种情况下是有用的:(1)在单CPU系统上,可以删除锁定前缀,至少在诸如DMA或其他在同一宏上竞争的非CPU代理等模糊情况之外。单CPU构建(即CONFIG_SMP=n)就是一个很好的例子-大多数原子操作隐藏在宏后面,当SMP被禁用时,宏可以简单地省略lock
前缀。如果没有lock
前缀,在遇到中断时仍然可以使用指令原子性,这正是您所需要的。@BeeOnRope:这一点很好。Linux实际上比这走得更远:在UP系统上引导SMP内核实际上是将lock
前缀补丁到0x90
NOP中所有在UP上不需要它的地方。如果禁用抢占,它还会修补一些锁定/解锁代码。(IIRC,内核日志消息是修补SMP备选方案
或其他内容)。关于在教学水平上具有原子性的极好观点;我可以看出非锁定的CMPXCHG实际上是如何有用的,就像这样。