Приращение целочисленного атома в x86?

На многоядерной машине x86 скажите, что поток, выполняющийся на core1, приращает целочисленную переменную a, в то же время поток на ядре 2 также увеличивает его. Учитывая, что начальное значение a равно 0, всегда было бы 2 в конце? Или это может иметь какую-то другую ценность? Предположим, что a объявлен как volatile, и мы не используем атомные переменные (такие как atom < > of С++ и встроенные атомарные операции в gcc).

Если значение a действительно было бы всегда 2 в таком случае, означает ли это, что a long int в x86-64 также будет иметь одно и то же свойство, то есть a всегда будет равным 2 в конец?

Ответы

Ответ 1

Инструкция машинной машины с расширением памяти на X86 является атомарной, только если вы используете ее с префиксом LOCK.

x ++ в C и С++ не имеет атомного поведения. Если вы делаете разблокированные приращения, из-за гонок, в которых процессор считывает и записывает X, если два отдельных процессора делают попытку увеличения, вы можете получить только одно приращение или оба будут видны (второй процессор, возможно, прочитал начальное значение, увеличил он и записал его обратно после того, как первый запишет его результаты назад).

Я считаю, что С++ 11 предлагает атомные приращения, и большинство компиляторов компиляторов имеют идиоматический способ вызвать атомное приращение некоторых встроенных целочисленных типов (обычно int и long); см. справочное руководство вашего компилятора.

Если вы хотите увеличить "большое значение" (скажем, целое число с целым числом), вам нужно сделать это с помощью некоторого стандартного механизма блокировки, такого как семафор.

Обратите внимание, что вам также нужно беспокоиться о атомных чтениях. На x86 чтение 32 или 64-битного значения бывает атомарным, если оно выровнено по 64-битовому слову. Это не относится к "большой ценности"; вам понадобится стандартная блокировка.

Ответ 2

Здесь одно доказательство не является атомарным в конкретной реализации (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 там, где этот объектный файл связан.

Ответ 3

Поскольку никто не ответил на ваш реальный вопрос и вместо этого показывает вам, как это сделать так, как всегда работает:

Thread 1 загружает значение 0

Резьба 2 загружает значение 0

В потоке 1 добавляются магазины 1

Thread 2 увеличивает свою локальную копию регистра и сохраняет 1.

Как вы можете видеть, конечным результатом является значение, равное 1, а не 2. Оно не всегда будет 2 в конце.

Ответ 4

Не гарантировано. Вы можете использовать команду lock xadd для достижения того же эффекта или использовать С++ std::atomic или использовать #pragma omp atomic или любое количество других concurrency решений, которые были написаны, чтобы избавить вас от необходимости изобретать колесо.