在多核 x86 机器上,假设在 core1 上执行的线程增加了一个整数变量a
,同时 core 2 上的线程也增加了它。假设 的初始值为a
0,那么它会一直2
到最后吗?或者它可能有其他价值?假设它a
被声明为volatile
并且我们没有使用原子变量(例如 C++ 的 atomic<> 和 gcc 中的内置原子操作)。
如果在这种情况下 的值a
确实总是 2,那是否意味着long int
x86-64 中的 a 也将具有相同的属性,即a
最终总是为 2?
X86 上的增量内存机器指令只有在您将其与LOCK前缀一起使用时才是原子的。
C 和 C++ 中的x++没有原子行为。如果您执行解锁增量,由于处理器正在读取和写入 X 的竞争,如果两个单独的处理器尝试增量,您最终可能只会看到一个增量或两者都被看到(第二个处理器可能已经读取了初始值,增量它,并在第一个写回结果后将其写回)。
我相信 C++11 提供了原子增量,并且大多数供应商编译器都有一种惯用的方式来导致某些内置整数类型(通常是 int 和 long)的原子增量;请参阅您的编译器参考手册。
如果你想增加一个“大值”(比如一个多精度整数),你需要使用一些标准的锁定机制,比如信号量。
请注意,您也需要担心 atomic reads。在 x86 上,读取 32 或 64 位值恰好是原子的,如果它是 64 位字对齐的。“大价值”并非如此。再次,您将需要一些标准锁。
这是一个证明它在特定实现(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
此目标文件时为那里填写结果内存地址。
由于没有人回答您的实际问题,而是向您展示如何以一种始终有效的方式进行操作:
线程 1 加载值为 0
线程 2 加载值为 0
线程 1 递增存储 1
线程 2 递增其本地寄存器副本的值并存储 1。
正如您所看到的,最终结果是一个等于 1 而不是 2 的值。最后并不总是 2。
不能保证。您可以使用该lock xadd
指令来实现相同的效果,或者使用 C++ std::atomic
,或者使用#pragma omp atomic
,或者已经编写的任何数量的其他并发解决方案,以节省您重新发明轮子的麻烦。