Что означает каждый порядок памяти?


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

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

  • memory_order_relaxed: не синхронизируется, но не игнорируется, когда заказ выполняется из другого режима в другом атомарном режиме. var
  • memory_order_consume: синхронизирует чтение этой атомной переменной, однако она не синхронизирует расслабленные vars, написанные до этого. Однако, если поток использует var X при изменении Y (и освобождает его). Другие потоки, потребляющие Y, также увидят X выпущенным? Я не знаю, означает ли это, что этот поток выталкивает изменения x (и, очевидно, y)
  • memory_order_acquire: синхронизирует чтение этой атомной переменной и гарантирует, что перед этим будут синхронизированы также и расслабленные vars. (означает ли это, что все атомарные переменные во всех потоках синхронизируются?)
  • memory_order_release: перемещает атомарное хранилище в другие потоки (но только если они читают var с помощью consume/acquire)
  • memory_order_acq_rel: для операций чтения/записи. Делает приобретение, чтобы вы не изменяли старое значение, и выпускает изменения.
  • memory_order_seq_cst: то же самое, что и получить релиз, за исключением того, что он заставляет обновления быть замеченными в других потоках (если a хранить с relaxed в другом потоке. Я храню b с помощью seq_cst. Один 3-й поток чтения a с relax будет видеть изменения вместе с b и любой другой атомной переменной?).
Я думаю, что понял, но поправьте меня, если я ошибаюсь. Я не смог найти ничего, что объясняло бы это на легком для чтения английском языке.
2 36

2 ответа:

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

(выдержка отредактирована, курсив добавлен)

Важно:

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

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

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

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

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

Далее следует моя собственная попытка более приземленного объяснения:]}

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

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

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

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

Операция

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

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

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

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

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

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

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

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

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

relaxed - никакие ограничения.

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

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

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

acq_rel - acquire и release вместе взятые.

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