Отличается ли atomic_thread_fence (memory_order_release) от использования memory_order_acq_rel?

cppreference.com предоставляет это примечание о std::atomic_thread_fence (выделено мной):

atomic_thread_fence накладывает более строгие ограничения синхронизации, чем операция атомарного хранилища с тем же std :: memory_order.

В то время как атомарная операция освобождения хранилища предотвращает перемещение всех предшествующих операций записи за пределы хранилища-освобождения, atomic_thread_fence с упорядочением memory_order_release предотвращает перемещение всех предшествующих операций записи за все последующие хранилища.

Я понимаю, что это примечание означает, что std::atomic_thread_fence(std::memory_order_release) не является однонаправленным, как магазин-релиз. Это двунаправленный забор, предотвращающий переупорядочивание магазинов, находящихся по обе стороны от забора, мимо магазина по другую сторону забора.

Если я правильно понимаю, этот забор дает те же гарантии, что и atomic_thread_fence(memory_order_acq_rel). Это забор «вверх» и забор «вниз».

Есть ли функциональная разница между std::atomic_thread_fence(std::memory_order_release) и std::atomic_thread_fence(std::memory_order_acq_rel)? Или разница чисто эстетическая, чтобы задокументировать цель кода?


person Drew Dormann    schedule 12.07.2018    source источник
comment
если я правильно это читаю, в выпуске упоминаются только магазины (пишет). Acquire - это загрузка (чтение), поэтому memory_order_acq_rel останавливает чтение и запись, пересекающую его, memory_order_release занимается только записью.   -  person Mike Vine    schedule 13.07.2018
comment
@MikeVine Я думаю, что здесь store означает std::atomic<>::store. Не обычная операция записи.   -  person curiousguy    schedule 13.12.2019


Ответы (3)


Автономная граница налагает более строгий порядок, чем атомарная операция с тем же ограничением порядка, но это не меняет направления, в котором применяется порядок.

Bot, операция атомарного выпуска и автономное ограждение выпуска являются однонаправленными, но атомарная операция приказывает по отношению к себе, тогда как атомарное ограждение устанавливает порядок по отношению к другим хранилищам.

Например, атомарная операция с семантикой выпуска:

std::atomic<int> sync{0};

// memory operations A

sync.store(1, std::memory_order_release);

// store B

Это гарантирует, что никакая часть операции с памятью A (загрузка и сохранение) не может быть (явно) переупорядочена с помощью самого атомарного хранилища. Но это однонаправлено, и правила упорядочения не применяются к операциям с памятью, которые выполняются после атомарной операции; следовательно, хранилище B все еще можно переупорядочить с помощью любой из операций с памятью в A.

Ограничение автономного выпуска изменяет это поведение:

// memory operations A

std::atomic_thread_fence(std::memory_order_release);

// load X

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

// stores B

Это гарантирует, что никакая операция с памятью в A не может быть (явно) переупорядочена с помощью любых хранилищ, которые упорядочены после ограничения выпуска. Здесь хранилище в B больше не может быть переупорядочено с помощью каких-либо операций с памятью в A, и поэтому ограничение выпуска сильнее, чем операция атомарного выпуска. Но он также является однонаправленным, поскольку загрузка из X все еще может быть переупорядочена с помощью любой операции с памятью в A.

Разница невелика, и обычно операция атомарного выпуска предпочтительнее изолированного выпуска.

Правила для отдельного ограждения для приобретения аналогичны, за исключением того, что он обеспечивает выполнение заказа в противоположном направлении и работает с нагрузками:

// loads B

sync.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_acquire);

// memory operations A

Ни одна операция с памятью в A не может быть переупорядочена с любой загрузкой, которая упорядочена до изолированного ограничения захвата.

Автономное ограждение с std::memory_order_acq_rel упорядочиванием объединяет логику как для приобретения, так и для освобождения ограждений.

// memory operations A
// load A

std::atomic_thread_fence(std::memory_order_acq_rel);

// store B
//memory operations B

Но это может стать невероятно сложным, если вы поймете, что хранилище в A все еще можно переупорядочить с загрузкой в ​​B. Вероятно, следует избегать ограждений Acq / rel в пользу обычных атомарных операций или, что еще лучше, мьютексов.

person LWimsey    schedule 13.07.2018
comment
В последнем сценарии вы говорите, что хранилище в A может происходить ниже нагрузки в B, возможно ли это с параметром acq / rel для операции? - person curiousguy12; 13.07.2018
comment
@ curiousguy12 Нет, этого не может произойти, если вы используете упорядочение acq / rel для атомарной операции (обратите внимание, что тогда это должно быть атомарное чтение-изменение-запись) - person LWimsey; 13.07.2018

cppreference.com допустил несколько ошибок в процитированном вами абзаце. Я выделил их следующим образом:

atomic_thread_fence накладывает более строгие ограничения синхронизации, чем операция атомарного хранилища с тем же std :: memory_order. В то время как атомарная операция освобождения хранилища предотвращает прохождение всех предшествующих операций записи (должны быть операциями с памятью, т. Е. Включая чтение и запись) за пределы освобождения хранилища (полное предложение должно быть : сама операция сохранения-освобождения), atomic_thread_fence с упорядочением memory_order_release предотвращает все предыдущие операции записи (должны быть операции с памятью, т. е. включая чтение и запись ) от прохождения всех последующих магазинов.

Перефразируя это:

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

Это цитируется здесь .

person Charles    schedule 29.07.2019

Это моя интерпретация намерения следующего текста, и я думаю, что это то, что было задумано. Кроме того, эта интерпретация верна с точки зрения модели памяти, но все же плохая, так как это неполное объяснение.

В то время как атомарная операция освобождения хранилища предотвращает прохождение всех предшествующих записей за пределами хранилища-релиза, atomic_thread_fence с упорядочением memory_order_release предотвращает перемещение всех предшествующих операций записи за все последующие хранилища.

Использование словосочетаний «хранить» и «писать» намеренно:

  • "хранилище" здесь означает хранилище объекта std::atomic<> (не только вызов std::atomic<>::store, а также присвоение, которое эквивалентно .store(value) или атомарной операции RMW);
  • «запись» здесь означает любую запись в память, нормальную (не атомарную) или атомарную.

Это двунаправленный забор, предотвращающий переупорядочивание магазинов по обе стороны забора мимо магазина по другую сторону забора.

Нет, вы упустили существенное различие, потому что оно было только подразумеваемым; выражены нечетко, слишком тонко - не годится для учебного текста!

В нем говорится, что ограничение выпуска не симметрично: предыдущий побочный эффект памяти, называемый «записью», ограничивается следующими операциями атомарного хранилища.

Даже с этим разъяснением оно неполное и поэтому является плохим объяснением: оно настоятельно предполагает, что ограничения выпуска существуют только для того, чтобы гарантировать, что запись (и только запись) завершена. Это не относится к делу.

Операция освобождения - это то, что я называю сигналом: «Я закончил». Он сигнализирует о том, что все предыдущие операции с памятью выполнены, завершены, видимы. Важно понимать, что упорядочиваются не только модификации (которые можно обнаружить по состоянию памяти), должно быть все в памяти.

Многие статьи о потоковых примитивах ошибочны в этом смысле.

person curiousguy    schedule 13.12.2019