Подтвердить что ты не робот

Чем отличаются memory_order_seq_cst и memory_order_acq_rel?

Магазины - это операции выпуска, а нагрузки - операции для обоих. Я знаю, что memory_order_seq_cst предназначен для наложения дополнительного общего порядка для всех операций, но я не могу построить пример, где это не так, если все memory_order_seq_cst заменены на memory_order_acq_rel.

Я что-то пропущу, или разница - это просто эффект документации, т.е. следует использовать memory_order_seq_cst, если вы намерены не играть с более расслабленной моделью и использовать memory_order_acq_rel при ограничении расслабленной модели?

4b9b3361

Ответ 1

http://en.cppreference.com/w/cpp/atomic/memory_order  Внизу есть хороший пример at the bottom, который работает только с memory_order_seq_cst. По существу, memory_order_acq_rel обеспечивает порядок чтения и записи относительно атомарной переменной, в то время как memory_order_seq_cst обеспечивает порядок чтения и записи глобально. То есть последовательно согласованные операции видны во всех потоках в одном и том же порядке.

Пример сводится к следующему:

bool x= false;
bool y= false;
int z= 0;

a() { x= true; }
b() { y= true; }
c() { while (!x); if (y) z++; }
d() { while (!y); if (x) z++; }

// kick off a, b, c, d, join all threads
assert(z!=0);

Операции в z защищены двумя атомарными переменными, а не одной, поэтому вы не можете использовать семантику acqu-release для обеспечения того, чтобы z всегда увеличивался.

Ответ 2

Все еще используйте определение и пример из memory_order. Но замените memory_order_seq_cst на memory_order_release в магазине и memory_order_acquire при загрузке.

Упорядочение Release-Acquire гарантирует все, что произошло, прежде чем store в одном потоке станет видимым побочным эффектом в потоке, который выполнил загрузку. Но в нашем примере ничего не происходит до сохранения как в thread0, так и в thread1.

x.store(true, std::memory_order_release); // thread0

y.store(true, std::memory_order_release); // thread1

Более того, без memory_order_seq_cst последовательное упорядочение thread2 и thread3 не гарантируется. Вы можете представить, что они становятся:

if (y.load(std::memory_order_acquire)) { ++z; } // thread2, load y first
while (!x.load(std::memory_order_acquire)); // and then, load x

if (x.load(std::memory_order_acquire)) { ++z; } // thread3, load x first
while (!y.load(std::memory_order_acquire)); // and then, load y

Итак, если thread2 и thread3 выполняются до thread0 и thread1, это означает, что x и y остаются ложными, таким образом, ++z никогда не затрагивается, z остается 0 и срабатывает assert.

Однако, если memory_order_seq_cst входит в изображение, он устанавливает один общий порядок изменения всех атомарных операций, которые так помечены. Таким образом, в thread2 x.load затем y.load; в thread3, y.load, а затем x.load - это точно.

Ответ 3

На ISA, таких как x86, где атомы отображаются на барьеры, а фактическая модель машины включает буфер хранилища:

  • Для хранилищ seq_cst требуется очистка буфера хранилищ, поэтому последующее чтение этого потока задерживается до тех пор, пока хранилище не станет глобально видимым.
  • acq_rel не очищает буфер хранилища. Обычные загрузки и хранилища x86 по существу имеют семантику acq и rel. (seq_cst плюс буфер хранилища с пересылкой хранилища.)

    Но атомарные операции RMW x86 всегда переводятся в seq_cst, потому что префикс x86 asm lock является полным барьером памяти. Другие ISA могут выполнять расслабленные или acq_rel RMW в asm.

https://preshing.com/20120515/memory-reordering-caught-in-the-act  является поучительным примером различия между хранилищем seq_cst и хранилищем простых выпусков. (На самом деле это mov + mfence против обычного mov в x86 asm. На практике xchg - более эффективный способ создать хранилище seq_cst на большинстве процессоров x86, но GCC использует mov + mfence)

Интересный факт: инструкция AArch64 STLR release-store на самом деле является последовательным выпуском. В аппаратном обеспечении он имеет загрузки/хранилища с relaxed или seq_cst и барьеры для получения других сильных сторон. Но, к сожалению, я думаю, что rel или acq_rel должны быть усилены до seq_cst, потому что нет барьера или инструкции, которая дает все, что нужно rel, не будучи даже сильнее и дороже. Некоторые другие ISA (например, PowerPC) имеют больший выбор барьеров и могут укрепиться до mo_rel или mo_acq_rel дешевле, чем mo_seq_cst.