Последовательная консистенция и атомарные операции

Вопрос из почты:

В C++ атомарные операции гарантируют соблюдение последовательной консистенции в многопоточном приложении? То есть при использовании атомарных операций есть гарантия того, что компилятор или процессоры будут инициализировать или выполнять в порядке отличном от порядка в исходном коде?

Например было

x = 1;

y = 2;

if (x == 1)

doStuff(y);

Ведь запросто может быть такое, что x будет отличным от 1. А если эти переменные будут атомарными?

Вообще для C++ стандарт заявляет какая модель памяти должна использоваться в параллельных вычислениях?

>В C++ атомарные операции гарантируют соблюдение последовательной консистенции в многопоточном приложении?

Надо понимать, что речь идёт о стандарте С++0х. С++0х гарантирует последовательную консистентность для программ, которые используют мьютексы и атомарные операции с упорядочиванием memory_order_seq_cst (значение по-умолчанию для всех атомарных операций). Если же используются атомарные операции с упорядочиванием отличным от memory_order_seq_cst, то последовательная консистентность не гарантируется (однако вполне чётко описано что же гарантируется и для них).

На всякий случай, последовательная консистентность (sequential consistency) — это когда мы можем объяснить любое выполнение программы на основе простого "промежения" (interleaving) кода потоков.

Соответсвенно, гарантии слабее последовательной консистентности обычно называют расслабленная (relaxed) модель памяти. И для неё есть такие допустимые выполнения программы, которые нельзя объяснить на основе промежения кода потоков. Например следующая программа может напечатать "0". Видно, что это нельзя объяснить просто выполняя действия то из одного потока, то из другого:

std::atomic<int> x; // = 0

std::atomic<int> y; // = 0

// thread 1

y.store(1, std::memory_order_relaxed);

x.store(1, std::memory_order_relaxed);

// thread 2

if (x.load(std::memory_order_relaxed) == 1)

printf("%d", y.load(std::memory_order_relaxed));

>То есть при использовании атомарных операций есть гарантия того, что компилятор или процессоры будут инициализировать или выполнять в порядке отличном от порядка в исходном коде?

Тут видимо имелось в виду "в порядке НЕ отличном". Для вышеуказанных случаев (мьютексы и memory_order_seq_cst) — да.

>Ведь запросто может быть такое, что x будет отличным от 1. А если эти переменные будут атомарными?

Естественно, может. Это уже не вопрос какой-то консистентности, если считать значение разделяемой переменной, а потом перечитать его через какое-то время, то, естественно, оно может уже измениться. Что бы этого не было есть только 2 варианта: (1) либо не перечитывать значение второй раз, либо (2) не менять переменную в других потоках.

По поводу "А если эти переменные будут атомарными?" необходимо отметить, что если эти переменные не атомарные, то это просто программа с гонками (data race), все такие программы попадают в номинацию UB. Т.е. разделяемые переменные в любом случае должны быть атомарными (std::atomic).

>Вообще для C++ стандарт заявляет какая модель памяти должна использоваться в параллельных вычислениях?

С++0х, как и любой другой подобный стандарт, ничего не навязывает в этом плане. Он просто предоставляет ряд возможностей, и описывает различные гарантии для них. Более конкретно, он предоставляет на выбор последовательно-консистентную модель памяти, расслабленную модель памяти, и говорит, что программы с гонками на не атомарных операциях — UB.