Я читаю эту статью Упорядочение памяти во время компиляции из которого сказано:
На самом деле, большинство вызовов функций действуют как барьеры компилятора,
содержат ли они свой собственный барьер компилятора или нет. Это исключает встроенные функции, функции, объявленные с чистым атрибутом, и случаи, когда используется генерация кода времени ссылки. Помимо этих случаев, вызов внешней функции даже сильнее, чем барьер компилятора, поскольку компилятор понятия не имеет, какими будут побочные эффекты функции.
Это правдивое утверждение? Подумайте об этом образце —
std::atomic_bool flag = false;
int value = 0;
void th1 () { // running in thread 1
value = 1;
// use atomic & release to prevent above sentence being reordered below
flag.store(true, std::memory_order_release);
}
void th2 () { // running in thread 2
// use atomic & acquire to prevent asset(..) being reordered above
while (!flag.load(std::memory_order_acquire)) {}
assert (value == 1); // should never fail!
}
Тогда мы можем удалить атомарный, но заменить на вызов функции —
bool flag = false;
int value = 0;
void writeflag () {
flag = true;
}
void readflag () {
while (!flag) {}
}
void th1 () {
value = 1;
writeflag(); // would function call prevent reordering?
}
void th2 () {
readflag(); // would function call prevent reordering?
assert (value == 1); // would this fail???
}
Любая идея?
Барьер компилятора — это не то же самое, что барьер памяти. Барьер компилятора предотвращает компилятор от перемещения кода через барьер. Барьер памяти (грубо говоря) предотвращает аппаратные средства от перемещения читает и пишет через барьер. Для атомарного режима вам нужны оба, и вам также нужно убедиться, что значения не порвутся при чтении или записи.
Формально нет, хотя бы потому, что генерация кода Link-Time является допустимым выбором реализации и не должна быть необязательной.
Есть также вторая оплошность, и это анализ побега. Утверждается, что «Компилятор не знает, какими будут побочные эффекты функции»., но если нет указателей на мой локальные переменные выходят из мой функции, то компилятор точно знает, что никакая другая функция не изменяет их.
Во втором примере, даже если мы предполагаем, что никакого переупорядочения нет, поведение не определено.
Флаг записи и чтения из переменной не является атомарным, и есть условие гонки1. Отсутствие переупорядочения не гарантирует, что оба потока не получат доступ к переменной flat одновременно. Это происходит, когда один поток попадает в цикл while в функции readflag и читает флаг, а другой поток выполняет запись в флаг в writeflag.
1 (Цитируется по: ISO / IEC 14882: 2011 (E) 1.10 Многопоточные исполнения и гонки данных 21)
Выполнение программы содержит гонку данных, если она содержит два конфликтующих действия в разных потоках,
по крайней мере, один из которых не является атомарным, и ни один не происходит раньше другого. Любая такая гонка данных приводит к
неопределенное поведение