在多核x86机器上,假设在core1上执行的线程递增整数变量a
,同时核心2上的线程也递增它。鉴于a
的初始值为0,最终总是2
?或者它可能有其他价值?假设a
被声明为volatile
并且我们没有使用原子变量(例如C ++的原子<>并且在gcc中内置原子操作)。
如果a
的值在这种情况下确实总是2,这是否意味着x86-64中的long int
也具有相同的属性,即a
到最后总是2?
答案 0 :(得分:27)
只有在使用 LOCK 前缀时,X86上的增量内存机器指令才是原子的。
C和C ++中的x ++ 没有原子行为。如果您执行解锁增量,由于处理器正在读取和写入X的比赛,如果两个单独的处理器尝试增加,您最终只能看到一个增量或两者都被看到(第二个处理器可能已经读取了初始值,增加了它,并在第一次写回结果后将其写回。)
我相信C ++ 11提供了原子增量,并且大多数供应商编译器都有惯用的方法来导致某些内置整数类型(通常是int和long)的原子增量;请参阅编译器参考手册。
如果要增加“大值”(例如,多精度整数),则需要使用一些标准锁定机制(如信号量)。
请注意,您还需要担心原子读取。在x86上,如果是64位字对齐,则读取32位或64位值恰好是原子的。对于“大价值”而言,情况并非如此;再次,你需要一些标准锁。
答案 1 :(得分:8)
这是一个证明它在特定实现(gcc)中不是原子的, 如您所见(?),gcc生成
代码这远非原子。
$ cat t.c
volatile int a;
void func(void)
{
a++;
}
[19:51:52 0 ~] $ gcc -O2 -c t.c
[19:51:55 0 ~] $ objdump -d t.o
t.o: file format elf32-i386
Disassembly of section .text:
00000000 <func>:
0: a1 00 00 00 00 mov 0x0,%eax
5: 83 c0 01 add $0x1,%eax
8: a3 00 00 00 00 mov %eax,0x0
d: c3 ret
不要被0x0
指令中的mov
所欺骗,那里有4个字节的空间,并且链接器将填充a
的结果内存地址。目标文件已链接。
答案 2 :(得分:8)
由于没有人回答你的实际问题,而是以一种始终有效的方式向你展示如何做到这一点:
线程1加载值0
线程2加载值0
线程1递增商店1
线程2递增其本地寄存器值的副本并存储1。
正如您所看到的,最终结果是一个等于1但不是2的值。最后它不会总是2。
答案 3 :(得分:5)
不保证。您可以使用lock xadd
指令来实现相同的效果,或者使用C ++ std::atomic
,或者使用#pragma omp atomic
,或者编写的任何其他并发解决方案来节省您的麻烦重新发明轮子。