Что означает каждый элемент memory_order?

Я прочитал главу, и мне это не очень понравилось. Мне все еще неясно, в чем различия между каждым порядком памяти. Это мое текущее предположение, которое я понял после прочтения гораздо более простого http://en.cppreference.com/w/cpp/atomic/memory_order

Ниже это неправильно, поэтому не пытайтесь учиться на этом

  • memory_order_relaxed: не синхронизируется, но не игнорируется, когда порядок сделан из другого режима в другом атомарном var
  • memory_order_consume: синхронизирует чтение этой атомарной переменной, однако не синхронизирует расслабленные переменные, написанные до этого. Однако, если поток использует var X при изменении Y (и освобождает его). Другие потоки, использующие Y, также увидят, что X выпущен? Я не знаю, означает ли это, что этот поток выталкивает изменения х (и, очевидно, у)
  • memory_order_acquire: Синхронизирует чтение этой атомарной переменной И гарантирует, что расслабленные переменные, записанные до этого, также синхронизируются. (Значит ли это, что все атомные переменные во всех потоках синхронизируются?)
  • memory_order_release: передает атомарное хранилище в другие потоки (но только если они читают переменную с использованием / получением)
  • memory_order_acq_rel: для операций чтения / записи. Выполняет ли приобретение, чтобы вы не изменяли старое значение и высвобождали изменения.
  • memory_order_seq_cst: то же самое, что и release release, за исключением того, что заставляет обновления быть видимыми в других потоках (если a Хранить с расслабленным на другом потоке. Я храню b с seq_cst. Чтение 3-го потока a с расслаблением увидим изменения вместе с b и любая другая атомная переменная?).

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

37

Решение

GCC Wiki дает очень подробное и простое для понимания объяснение с примерами кода.

(отредактированный отрывок и выделение добавлено)

ВАЖНЫЙ:

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

Первая модель является «последовательно последовательной». Этот режим используется по умолчанию, если он не указан, и является наиболее ограничительным. Это также может быть явно указано через memory_order_seq_cst, Это обеспечивает те же ограничения и ограничения для перемещения нагрузки вокруг, с которыми последовательные программисты знакомы по своей сути, за исключением того, что они применяются ко всем потокам.
[…] С практической точки зрения это сводится ко всем атомным операциям, выступающим в качестве барьеров для оптимизации. Можно переупорядочивать вещи между атомарными операциями, но не между операциями. Локальное содержимое потока также не затронуто, так как нет никакой видимости другим потокам. […] Этот режим также обеспечивает согласованность все потоки.

противоположный подход является memory_order_relaxed, Эта модель обеспечивает гораздо меньшую синхронизацию, устраняя ограничения, возникающие до того. У этих типов атомарных операций также могут быть различные оптимизации, такие как удаление мертвых хранилищ и их объединение. […] Без каких-либо ребер, предшествующих событию, ни один поток не может рассчитывать на конкретный порядок в другом потоке.
Расслабленный режим чаще всего используется, когда программист просто хочет, чтобы переменная была атомарной по своей природе вместо того, чтобы использовать его для синхронизации потоков для других данных общей памяти.

Третий режим (memory_order_acquire / memory_order_release) это гибридный между двумя другими. Режим получения / выпуска аналогичен последовательно согласованному режиму, за исключением того, что только применяет отношение «происходит до» к зависимым переменным. Это позволяет ослабить синхронизацию, требуемую между независимыми чтениями независимых записей.

memory_order_consume является еще одним тонким уточнением модели памяти выпуска / приобретения, которое слегка ослабляет требования удаление происходит перед упорядочением и по независимым переменным общего доступа.
[…] Реальная разница сводится к тому, в каком состоянии аппаратное обеспечение должно быть сброшено для синхронизации. Поскольку операция потребления может поэтому выполняйте быстрее, кто-то, кто знает, что он делает, может использовать его для приложений, критичных для производительности

Вот моя собственная попытка более мирского объяснения:

Другой подход к этому — взглянуть на проблему с точки зрения переупорядочения операций чтения и записи, как атомарных, так и обычных:

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

Теперь расслабленный это просто, минимум. Кроме того, он ничего не делает и не предоставляет никаких других гарантий. Это самая дешевая операция. Для операций чтения-изменения-записи на строго упорядоченных процессорных архитектурах (например, x86 / amd64) это сводится к обычному обычному ходу.

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

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

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

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

В настоящее время не рекомендуется использовать потреблять заказ в то время как спецификация пересматривается.

38

Другие решения

Это довольно сложный предмет. Попробуй прочитать http://en.cppreference.com/w/cpp/atomic/memory_order несколько раз попробуйте прочитать другие ресурсы и т. д.

Вот упрощенное описание:

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

Обычно вы можете использовать блокировки для синхронизации. Проблема в том, что они медленные. Атомарные операции выполняются намного быстрее, поскольку синхронизация происходит на уровне ЦП (т. Е. ЦП гарантирует, что никакой другой поток, даже на другом ЦП, не изменит какую-либо переменную и т. Д.).

Итак, единственная проблема, с которой мы сталкиваемся, это изменение порядка доступа к памяти. memory_order enum указывает, какие типы компиляторов переупорядочений должен запретить.

relaxed — нет ограничений.

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

acquire — никакие грузы не могут быть переупорядочены в отношении. атомная нагрузка. То есть если они после атомной нагрузки в исходном коде, они будут случаться после атомной нагрузки тоже.

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

acq_relacquire а также release вместе взятые.

seq_cst — сложнее понять, почему требуется этот порядок. По сути, все остальные упорядочения гарантируют, что определенные запрещенные переупорядочения не будут происходить только для потоков, которые потребляют / выпускают одну и ту же атомарную переменную. Доступ к памяти все еще может распространяться на другие потоки в любом порядке. Этот порядок гарантирует, что этого не произойдет (таким образом, последовательная последовательность). Для случая, когда это необходимо, смотрите пример в конце связанной страницы.

23