Приращение целочисленного атома в 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 решений, которые были написаны, чтобы избавить вас от необходимости изобретать колесо.