今天我遇到了这个问题:

你有一个代码

static int counter = 0;
void worker() {
    for (int i = 1; i <= 10; i++)
        counter++;
}

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

我知道实际上它可以是任何东西。但我的内心告诉我, counter++ 最有可能被翻译成单个汇编指令,并且如果两个线程在同一个核心上执行, counter 将会是20。

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

有帮助吗?

解决方案

特别针对 x86,并且关于您的示例: counter++, ,有多种编译方法。最简单的例子是:

inc counter

这转化为以下微操作:

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

这本质上与以下相同:

mov eax, counter
inc eax
mov counter, eax

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

如果你想保证这个 inc 是原子的,使用 lock 字首:

lock inc counter

lock 保证无人可以更新 counter 负载和存储之间。


对于更复杂的指令,您通常不能假设它们会自动执行,除非它们支持 lock 字首。

其他提示

答案是:这取决于

下面是一些混乱周围,一个汇编指令是什么。通常情况下,一个汇编指令翻译成只有一个机器指令。该excemption是当您使用宏 - 但你应该意识到这一点。

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

在过去的好时光,那是。但今天,随着复杂的CPU,长时间运行的指令,超线程,......事实并非如此。某些CPU保证的一些的递增/递减指令是原子。其原因是,它们是对于整齐非常简单的同步处理资料。

另外一些CPU命令不那么成问题。当你有一个简单读取(的一个数据,该处理器可以整体提取) - 抓取本身当然是原子的,因为没有什么可以在所有的分割。但是,当你有未对齐的数据,就再次变得复杂了。

答案是:这取决于。仔细阅读销售商的机器指令手册。在疑问,这是不!

编辑: 哦,我现在看出来了,你还问++计数器。声明“最有可能被翻译成”不能在所有的信任。这在很大程度上也取决于课程的编译器!它变得更加困难当编译器制造不同的优化。

不会总是 - 在某些体系结构一个汇编指令翻译成一个机器代码指令,而在其它它不

除了 - 你可以的从未假设你正在使用的程序语言是编译一个看似简单的代码行到一个汇编指令。此外,在一些结构中,不能假定一个机器代码将执行原子

使用适当的同步技术,而不是依赖于你中编码的语言。

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

已失效由Nathan的评论: <击>如果我没有记错我的英特尔x86汇编,INC指令只适用于登记,不直接对内存位置运行。

<击>

<击>因此,一个计数器++不会在汇编一个单一指令(只忽略增量后的部分)。这将是至少三个指令:负载柜变量进行登记,增量寄存器,加载寄存器回到柜台。而这仅仅是为x86架构。

在短,不依赖于,除非它是由语言规范中指定,并且所使用的编译器支持的规格它是原子的。

不,你不能假设这一点。除非在编译器规范明确规定。而且谁也不能保证一个单一的汇编指令的确原子。实际上,每个汇编指令转换为微操作的数量 - 微指令。 结果,另外的竞争条件的问题是紧耦合存储器与模型(相干性,顺序,松开的连贯性和等),对于每一个答案和结果可以是不同的。

另一个问题是,如果你不变量声明为挥发,生成的代码可能不会在每次循环只在循环的记忆将被更新的最后更新的内存。

可能不是一个实际的回答你的问题,但(假设这是C#或其他.NET语言),如果你想counter++真的是多线程的原子,你可以使用System.Threading.Interlocked.Increment(counter)

请参见上有很多不同的方式,为什么/如何counter++不能原子的实际信息,其他的答案。 ; - )

在大多数情况下,的没有即可。事实上,在x86,可以执行该指令

push [address]

其中,在C,会是这样的:

*stack-- = *address;

此进行的在一个指令的两个存储转移

这基本上不可能在1个时钟周期,而不是至少是因为一个存储器传输也不可能做在一个周期!

在许多其它的处理器,存储器系统和处理器之间的分离 - 较大。 (通常这些处理器可根据存储系统很少或大端,如ARM和PowerPC),这也有原子行为的后果,如果存储系统可以重新排序的读取和写入。

为了这个目的,有记忆力障碍( http://en.wikipedia.org/wiki/ Memory_barrier

因此,在短期,而原子指令有足够的英特尔(连同有关锁前缀),更必须在非英特尔来完成,由于存储器的I / O可能不以相同的顺序。

这是从英特尔移植“无锁定”的解决方案,其它架构当一个已知的问题。

(注意,多处理器(未多核)x86系统似乎也需要存储的障碍,至少在64位模式。

我认为你会得到一个访问的竞争条件。

如果你想确保在递增计数器,一个原子操作,那么你就需要使用++柜台。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top