addl, $9, _x(%rip)
_x是一个全局变量。基本上我不确定在这种情况下如何实现添加到全局变量以及在多处理器系统中是否存在与此线路相关的固有竞争条件。
答案 0 :(得分:8)
正如duskwuff指出的那样,你需要一个lock
前缀。
原因是:
addl $9,_x(%rip)
实际上是三个"微操作"从记忆系统的角度来看[这里%eax
只是为了说明 - 从未真正使用过]:
mov _x(%rip),%eax
addl $9,%eax
mov %eax,_x(%rip)
这是一系列有效的事件。这由lock
前缀保证。最后,_x
将为18:
# this is a valid sequence
# cpu 1 # cpu 2
mov _x(%rip),%eax
addl $9,%eax
mov %eax,_x(%rip)
mov _x(%rip),%eax
addl $9,%eax
mov %eax,_x(%rip)
但是,如果没有lock
,我们可以得到:
# this is an invalid sequence
# cpu 1 # cpu 2
mov _x(%rip),%eax
mov _x(%rip),%eax
addl $9,%eax addl $9,%eax
mov %eax,_x(%rip)
mov %eax,_x(%rip)
最后,_x
将为9.序列的另一个混乱可能产生18.因此,根据两个CPU上的微操作之间的确切顺序,我们可以 < / em> 9或18。
我们可以让它变得更糟。如果CPU 2添加了8而不是9,则没有 lock
的序列可以产生以下任何一个:8,9或17
<强>更新强>
基于一些评论,只是为了澄清一些术语。
当我说微操作时......它是引号,所以我在本文中讨论了一个术语。 不意味着直接转换为x86处理器文献中定义的x86 uops。我可以[也许应该有]说步骤。
同样,尽管使用x86 asm表达步骤似乎最容易和最清楚,但我可能更抽象:
(1) FETCH_MEM_TO_MREG _x
(2) ADD_TO_MREG 9
(3) STORE_MREG_TO_MEM _x
不幸的是,这些步骤纯粹是在硬件逻辑中执行的(即程序无法通过调试器查看它们或逐步执行它们)。存储器系统(例如,高速缓存逻辑,DRAM控制器等)将注意到(并且必须响应)步骤(1)和(3)。 CPU的ALU将执行步骤(2),这对于存储器逻辑是不可见的。
请注意,某些RISC CPU拱门没有添加适用于内存的指令,也没有锁定前缀。见下文。
除了阅读一些文献之外,检查效果的一种实用方法是创建一个使用多个线程(通过pthreads
)并使用一些C原子操作和/或pthread_mutex_lock
的C程序。 / p>
另外,这个页面Atomically increment two integers with CAS有我给出的答案,还有一个链接到另一个人在cppcon给出的视频讲话(关于&#34;无锁&#34;实现)
在这个更通用的模型中,它还可以说明在没有正确记录锁定的数据库中会发生什么。
实现lock
的实际机制可以是x86模型特定的。
并且,可能的是,目标指令具体(例如lock
如果目标指令是[比较] addl
vs xchg
,则工作方式不同,因为处理器可能能够更有效地使用/特殊类型的内存周期(例如像原子&#34;读 - 修改 - 写&#34;)。
在其他情况下(例如,对于单个周期数据太宽或跨越缓存行边界),它可能必须锁定整个内存总线(例如,获取全局锁定并强制完全序列化),执行多次读取,进行更改,执行多次写入,然后解锁内存总线。此模式类似于如何将内容包装在互斥锁定/解锁配对中,仅在内存总线逻辑级别的硬件中完成
关于ARM [RISC cpu]的说明。 ARM仅支持ldr r1,memory_address
,str r1,memory_address
,但不支持 add r1,memory_address
。它只允许add r1,r2,r3
[即它是&#34;三元&#34;]或可能是add r1,r2,#immed
。要实现锁定,ARM有两条特殊说明:ldrex
和strex
必须配对。在上面的抽象模型中,它看起来像:
ldrex r1,_x
add r1,r1,#9
strex r1,_x
// must be tested for success and loop back if failed ...
答案 1 :(得分:3)
没有。处理器之间有一个小窗口,读取旧值_x
并将新值写回;如果另一个CPU在该确切时刻写入_x
,则该值将被覆盖。
在指令中添加LOCK
前缀将使操作成为原子。