今天我遇到了这个问题:
你有一个代码
static int counter = 0;
void worker() {
for (int i = 1; i <= 10; i++)
counter++;
}
如果worker
会从两个不同的线程调用,将什么样的价值counter
有他们两个都完成之后?
我知道,其实它可以是任何东西。 但我的胆量内告诉我,那个counter++
将最有可能被翻译成单一的汇编指令,如果两个线程都执行相同的核心, counter
将是20。
但是,如果这些线程在不同的内核或处理器上运行,也可以在他们的微码中的竞争条件? 是一个汇编指令总是可以被看作是原子操作?
Answer 1:
特别适用于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
前缀。
Answer 2:
答案是:不一定!
下面是一些混乱周围的汇编指令是什么。 通常情况下,一个汇编指令翻译成只有一个机器指令。 该excemption是当您使用宏 - 但你应该意识到这一点。
这就是说,这个问题归根结底是一个机器指令原子?
在过去的好时光,那是。 但今天,随着复杂的CPU,长时间运行的指令,超线程,......事实并非如此。 某些CPU保证一些递增/递减指令是原子。 原因是,他们整齐的很简单的同步处理资料。
还有些CPU的命令并没有那么困难。 当你有一个简单读取(的一个数据,该处理器可以整体提取) - 抓取本身当然是原子的,因为没有什么可以在所有的分割。 但是,当你有未对齐的数据,再次变得复杂。
答案是:这要看情况。 仔细阅读销售商的机器指令手册。 如有疑问,它是不是!
编辑:哦,我现在看出来了,你还问++计数器。 声明“最有可能被翻译成”不能在所有的信任。 这在很大程度上也取决于课程的编译器! 当编译器进行不同的优化问题就变得更困难。
Answer 3:
并非总是如此 -在一些系统上一个汇编指令翻译成一个机器代码指令,而别人没有。
此外 -你不能假设你正在使用的程序语言编写一个看似简单的代码行到一个汇编指令。 此外,在一些体系,你不能假设一个机器代码将原子执行。
使用适当的同步技术,而不是依赖于你中编码的语言。
Answer 4:
- 在单32位处理器没有超线程技术在32位或更小的整数变量递增/递减操作都是原子。
- 在具有超线程技术和多处理器系统上的处理器,该递增/递减操作,但不保证atomicaly执行。
Answer 5:
由Nathan的评论无效: 如果我没有记错我的英特尔x86汇编,INC指令只适用于登记,不直接对存储位置进行工作。
因此,一个计数器++不会在汇编单个指令(只是忽略了后增量部分)。 这将是至少三个指令:负载柜变量进行登记,增量寄存器,加载寄存器回到柜台。 而这仅仅是为x86架构。
总之,不靠,除非它是由语言规范规定和您正在使用的编译器支持的规格它是原子。
Answer 6:
不,你不能假设这一点。 除非在编译器规范明确规定。 而且谁也不能保证一个单一的汇编指令的确原子。 实际上,每个汇编指令转换为微操作的数量 - 微指令。
还的竞态条件的问题是紧耦合存储器与模型(相干性,顺序的,释放的一致性和等),对于每一个答案和结果可以是不同的。
Answer 7:
另一个问题是,如果你不变量声明为挥发,生成的代码可能不会在每次循环只在循环的记忆将被更新的最后更新的内存。
Answer 8:
可能不是一个实际的回答你的问题,但(假设这是C#或其他.NET语言),如果你想counter++
,真正成为多线程的原子,你可以使用System.Threading.Interlocked.Increment(counter)
。
看到在许多不同的方式,为什么/如何实际资料其他答案counter++
不能原子。 ;-)
Answer 9:
在大多数情况下, 没有 。 事实上,在x86上,您可以执行指令
push [address]
其中,在C,会是这样的:
*stack-- = *address;
这执行一个指令的两个存储传输 。
这基本上是不可能在1个时钟周期做,并非最不重要的,因为一个内存传输也不可能在一个周期!
Answer 10:
在许多其它的处理器,存储器系统和处理器之间的分离 - 较大。 (通常这些处理器可根据存储系统很少或大端,如ARM和PowerPC),这也有原子行为的后果,如果存储系统可以重新排序的读取和写入。
为此,有记忆障碍( http://en.wikipedia.org/wiki/Memory_barrier )
因此,在短期,而原子指令有足够的英特尔(有关锁前缀),更必须在非Intel完成,由于存储I / O可能不会以相同的顺序。
移植“无锁”从英特尔其他架构的解决方案时,这是一个已知的问题。
(注意在x86的是多处理器(未多核)系统似乎也需要存储的障碍,至少在64位模式。
Answer 11:
我认为你会得到一个访问的竞争条件。
如果你想确保在递增计数器,一个原子操作,那么你就需要使用++计数器。
文章来源: Does one assembler instruction always execute atomically?