Atomic 64-битная запись с GCC
Я запутался в многопоточном программировании и надеялся, что кто-то может прийти и похлопывать меня каким-то пониманием.
Проделав довольно много чтения, я пришел к пониманию, что могу установить значение 64-битного int атомарно в 64-битной системе 1.
Я нашел много этого чтения трудным, хотя, поэтому подумал, что я попытаюсь сделать тест, чтобы проверить это. Поэтому я написал простую программу с одним потоком, который установил бы переменную в одно из двух значений:
bool switcher = false;
while(true)
{
if (switcher)
foo = a;
else
foo = b;
switcher = !switcher;
}
И еще один поток, который будет проверять значение foo
:
while (true)
{
__uint64_t blah = foo;
if ((blah != a) && (blah != b))
{
cout << "Not atomic! " << blah << endl;
}
}
Я установил a = 1844674407370955161;
и b = 1144644202170355111;
. Я запускаю эту программу и не получаю никаких предупреждений о том, что blah
не a
или b
.
Отлично, похоже, что это, вероятно, атомная запись... но затем я изменил первый поток, чтобы установить a
и b
напрямую, например:
bool switcher = false;
while(true)
{
if (switcher)
foo = 1844674407370955161;
else
foo = 1144644202170355111;
switcher = !switcher;
}
Я снова запустил и вдруг:
Not atomic! 1144644203261303193
Not atomic! 1844674406280007079
Not atomic! 1144644203261303193
Not atomic! 1844674406280007079
Что изменилось? В любом случае я назначаю большое число foo
- компилятор обрабатывает постоянное число по-другому или я все неправильно понял?
Спасибо!
<ч/" > 1: Документация Intel CPU, раздел 8.1, Гарантированные Atomic Operations
2: Список разработчиков GCC, в котором обсуждается, что GCC не гарантирует его в документации, но ядро и другие программы полагаются на него
Ответы
Ответ 1
Разборка цикла, я получаю следующий код с gcc
:
.globl _switcher
_switcher:
LFB2:
pushq %rbp
LCFI0:
movq %rsp, %rbp
LCFI1:
movl $0, -4(%rbp)
L2:
cmpl $0, -4(%rbp)
je L3
movq [email protected](%rip), %rax
movl $-1717986919, (%rax)
movl $429496729, 4(%rax)
jmp L5
L3:
movq [email protected](%rip), %rax
movl $1486032295, (%rax)
movl $266508246, 4(%rax)
L5:
cmpl $0, -4(%rbp)
sete %al
movzbl %al, %eax
movl %eax, -4(%rbp)
jmp L2
LFE2:
Итак, похоже, что gcc
использует 32-разрядную инструкцию movl
с 32-битными непосредственными значениями. Существует инструкция movq
, которая может перемещать 64-разрядный регистр в память (или память в 64-разрядный регистр), но, похоже, не может установить немедленное перемещение адреса памяти, поэтому компилятор вынужден либо использовать временный регистр, а затем переместить значение в память или использовать для movl
. Вы можете попытаться заставить его использовать регистр с помощью временной переменной, но это может не сработать.
Литература:
Ответ 2
http://www.x86-64.org/documentation/assembly.html
непосредственные значения внутри инструкций остаются 32 бит.
Невозможно, чтобы компилятор выполнял назначение 64-битных констант атомарно, за исключением первого заполнения регистра, а затем перемещения этого регистра в переменную. Это, вероятно, более дорогостоящее, чем назначение непосредственно переменной, и поскольку атомарность не требуется языком, атомное решение не выбирается.
Ответ 3
Документация Intel CPU правильная, выровненная 8 байтов. Чтение/запись всегда является атомарным для последнего оборудования (даже в 32-разрядных операционных системах).
Что вы нам не говорите, используете ли вы 64-битное оборудование на 32-битной системе? Если это так, 8-байтная запись, скорее всего, будет разделена на две записи по 4 байта компилятором.
Просто взгляните на соответствующий раздел в объектном коде.