一个汇编指令总是以原子方式执行吗?

vav*_*ava 13 assembly multithreading atomic race-condition

今天我遇到了这个问题:

你有一个代码

static int counter = 0;
void worker() {
    for (int i = 1; i <= 10; i++)
        counter++;
}
Run Code Online (Sandbox Code Playgroud)

如果worker从两个不同的线程调用,那么两个线程counter完成后会有什么值?

我知道它实际上可能是任何东西.但是我的内部胆量告诉我,这counter++很可能会被翻译成单个汇编指令,如果两个线程都在同一个核心上执行,那么counter将是20.

但是,如果这些线程在不同的内核或处理器上运行,那么它们的微代码中是否存在竞争条件呢?是否可以将一个汇编指令视为原子操作?

Nat*_*man 19

特别是对于x86,关于你的例子:counter++,有很多方法可以编译.最简单的例子是:

inc counter
Run Code Online (Sandbox Code Playgroud)

这转化为以下微操作:

  • 加载counter到CPU上的隐藏寄存器
  • 递增寄存器
  • 存储更新的寄存器 counter

这基本上与以下相同:

mov eax, counter
inc eax
mov counter, eax
Run Code Online (Sandbox Code Playgroud)

请注意,如果counter加载和商店之间的某些其他代理更新,它将不会counter在商店之后反映出来.此代理可以是同一内核中的另一个线程,同一CPU中的另一个内核,同一系统中的另一个CPU,甚至是使用DMA(直接内存访问)的某个外部代理.

如果您想保证这inc是原子的,请使用lock前缀:

lock inc counter
Run Code Online (Sandbox Code Playgroud)

lock保证没有人可以counter在加载和商店之间进行更新.


关于更复杂的指令,您通常不能假设它们将以原子方式执行,除非它们支持lock前缀.


Yuv*_*dam 7

并非总是如此 - 在某些体系结构中,一个汇编指令被转换为一个机器代码指令,而在另一些体系结构指

另外 - 你永远不能假设你正在使用的程序语言正在将一段看似简单的代码编译成一个汇编指令.而且,在某些体系结构中,您不能假设一个机器代码将以原子方式执行.

请使用适当的同步技术,具体取决于您编写的语言.

  • 一个汇编指令可以转换为许多微码指令 (3认同)

Jue*_*gen 7

答案是:这取决于!

这里有一些混乱,汇编指令是什么.通常,一个汇编程序指令被转换为恰好一个机器指令.例外情况是你使用宏 - 但你应该知道这一点.

那说,问题归结为一个机器指令原子?

在过去的好时光中,它是.但今天,复杂的CPU,长时间运行的指令,超线程,......事实并非如此.某些CPU保证某些递增/递减指令是原子的.原因是,它们非常简单,可以进行非常简单的同步.

一些CPU命令也没有那么成问题.当你有一个简单的提取(处理器可以一件获取的一个数据) - 提取本身当然是原子的,因为没有任何东西可以分开.但是当你有未对齐的数据时,它会再次变得复杂.

答案是:这取决于.仔细阅读供应商的机器使用说明书.有疑问,它不是!

编辑:哦,我现在看到了,你也要求++计数器."最有可能被翻译"的陈述根本不可信.这在很大程度上还取决于编译器当然!当编译器进行不同的优化时,它会变得更加困难.

  • 我赞成"有些CPU保证一些递增/递减指令是原子的",我强调*some*.另外,请注意,即使是简单的提取也不一定是原子的.例如,如果它穿过高速缓存行边界,则可以分为两部分.如果数据类型大于高速缓存行,则即使对齐数据也是如此. (2认同)

Dar*_*rov 5

  1. 在没有超线程技术的单个32位处理器上对32位或更少整数变量的递增/递减操作是原子的.
  2. 在具有超线程技术的处理器上或在多处理器系统上,增量/减量操作不保证在原子上执行.

  • 这是错的.即使在单线程单CPU系统上,您仍然可以使用外部代理通过DMA或PCI更新内存.即使是简单的_inc memory_指令也不应该被认为是原子的.你还是要使用_lock inc memory_ (3认同)
  • Nathan Fellman是正确的,共享内存的增量不保证是原子的,除非你使用内存锁,即使在单核CPU上,你的线程也可以在读取和增量之间被抢占,或者某些其他机制如DMA可以创建一个竞争对手你的更新. (2认同)