在 x86 中增量是整数原子

Is increment an integer atomic in x86?

本文关键字:整数 x86      更新时间:2023-10-16

在多核 x86 机器上,假设在 core1 上执行的线程递增一个整数变量a同时核心 2 上的线程也会递增它。假设a的初始值为 0,最终会一直2吗?或者它可能有其他价值?假设a被声明为 volatile 并且我们没有使用 C++ 的原子变量(例如原子<>和 gcc 中内置的原子操作(。

如果在这种情况下a的值确实始终为 2,这是否意味着 x86-64 中的long int也将具有相同的属性,即a最终将始终为 2?

X86 上的增量内存机器指令仅在与 LOCK 前缀一起使用时才是原子指令。

C 和 C++ 中的 x++ 没有原子行为。如果执行解锁增量,由于处理器正在读取和写入 X 的争用,如果两个单独的处理器尝试增量,则最终可能只看到一个增量或同时看到两个增量(第二个处理器可能已读取初始值,递增它,并在第一个处理器写回其结果后将其写回(。

我相信 C++11 提供原子增量,大多数供应商编译器都有一种惯用的方法来导致某些内置整数类型(通常是 int 和 long(的原子增量;请参阅您的编译器参考手册。

如果要递增"大值"(例如,多精度整数(,则需要使用一些标准锁定机制(如信号量(来实现。

请注意,您还需要担心原子读取。 在 x86 上,如果读取 32 位或 64 位值是 64 位字对齐的,则读取该值恰好是原子值。 对于"大价值"来说,情况并非如此;同样,您需要一些标准锁。

这里有一个证明它在特定实现(gcc(中不是原子的,如您所见(?(,gcc 生成的代码是

  1. 将值从存储器加载到寄存器
  2. 递增寄存器的内容
  3. 将寄存器保存回内存。

这远非原子。

$ 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

不要被mov指令中的0x0所迷惑,那里有 4 个字节的空间,链接器将填充生成的内存地址,以便在链接此对象文件时a那里。

因为没有人回答你的实际问题,而是向你展示如何以一种始终有效的方式做到这一点:

线程 1 加载值 0

线程 2 加载值 0

线程 1 递增存储 1

线程 2 递增其值的本地寄存器副本并存储 1。

如您所见,最终结果是等于 1 而不是 2 的值。 最后不会总是 2

不能保证。您可以使用 lock xadd 指令来实现相同的效果,或者使用 C++ std::atomic 、 或使用 #pragma omp atomic 或为节省重新发明轮子的麻烦而编写的任何其他并发解决方案。