Запонки памяти: приобретать/загружать и выпускать/хранить

Мое понимание std::memory_order_acquire и std::memory_order_release выглядит следующим образом:

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

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

То, что я не понимаю, - это то, почему в библиотеке атоматики С++ 11, в частности, сборщик привязок связан с операциями загрузки, в то время как забор забора связан с операциями хранилища.

Чтобы уточнить, библиотека С++ 11 <atomic> позволяет вам указать ограждения памяти двумя способами: либо вы можете указать забор как дополнительный аргумент для атомной операции, например:

x.load(std::memory_order_acquire);

Или вы можете использовать std::memory_order_relaxed и указать забор отдельно, например:

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

Что я не понимаю, учитывая приведенные выше определения получения и выпуска, почему С++ 11 специально ассоциирует приобретать с загрузкой и выпускает в магазине? Да, я видел много примеров, которые показывают, как вы можете использовать получение/загрузку с выпуском/хранилищем для синхронизации между потоками, но в целом кажется, что идея получения забора (предотвращение переопределения памяти после оператора) и выпуск забор (предотвращение переупорядочения памяти перед оператором) ортогонален идее нагрузок и хранилищ.

Итак, почему, например, компилятор не позволяет мне сказать:

x.store(10, std::memory_order_acquire);

Я понимаю, что могу выполнить выше, используя memory_order_relaxed, а затем отдельный оператор atomic_thread_fence(memory_order_acquire), но опять же, почему я не могу использовать хранилище напрямую с memory_order_acquire?

Возможно, это может быть случай использования, если я хочу убедиться, что некоторое хранилище, скажем x = 10, происходит до того, как выполняется какой-либо другой оператор, который может повлиять на другие потоки.

Ответ 1

Скажем, я пишу некоторые данные, а затем пишу указание на то, что данные теперь готовы. Необходимо, чтобы ни один другой поток, который видит указание на то, что данные готовы, не видит записи самих данных. Поэтому предыдущие записи не могут двигаться дальше, чем писать.

Скажем, я прочитал, что некоторые данные готовы. Крайне важно, чтобы все прочитанные мной сообщения я узнал после того, как увидели, что они произошли после чтения, которые видели, что данные были готовы. Поэтому последующие чтения не могут двигаться позади этого чтения.

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

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

Ответ 2

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

В качестве альтернативы вы можете сказать

x.store(10, std::memory_order_releaxed);
x.load(std::memory_order_acquire);  // this introduce a data dependency

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

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

См. также формальное описание и explanation для деталь.