http://preshing.com/20120710/memory-barriers-are-like-source-control-operations/ объясняет различные виды барьеров, таких как LoadLoad или StoreStore.Барьер StoreStore только предотвращает переупорядочение магазинов через барьер, но загрузка все равно может выполняться не по порядку.
На реальных процессорах любые барьеры, которые включают StoreLoad, также блокируют все остальное и поэтому называются «полными барьерами»,StoreLoad - самый дорогой тип, потому что это означает опустошение буфера хранилища, прежде чем последующие загрузки смогут прочитать из кэша L1d.
Примеры барьеров:
strong weak
x86 mfence none needed unless you're using NT stores
ARM dmb sy isb, dmb st, dmb ish, etc.
POWER hwsync lwsync, isync, ...
ARM имеет «внутренние» и «внешние общие совместно используемые домены».».Я действительно не знаю, что это значит, мне не приходилось с этим сталкиваться, но эта страница документирует различные доступные формы барьера памяти данных.dmb st
ожидает только завершения предыдущих хранилищ, поэтому я думаю, что это всего лишь барьер StoreStore, и поэтому он слишком слаб для хранилища релизов C ++ 11, которому также необходимо упорядочить более ранние загрузки для переупорядочения LoadStore.См. Также C / C ++ 11 отображений для процессоров : обратите внимание, что seq-cst может быть достигнут с полными барьерами вокруг каждого магазина или с барьерами перед загрузкой, а также перед магазинами.Делать грузы дешевле, как правило, лучше всего.
ARM ISB очищает кэши инструкций.(У ARM нет связного i-кеша, поэтому после записи кода в память вам необходим ISB, прежде чем вы сможете надежно перейти туда и выполнить эти байты в качестве инструкций.)
В POWER имеется большой выбор доступных барьеров, в том числе легкий (неполный барьер) и тяжелый синхронизатор (полный барьер), упомянутые в статье Джеффа Прешинга, ссылка на которую приведена выше.
Однонаправленный барьер - это то, что вы получаете отрелиз-магазин или приобретение-загрузка .Хранилище релизов в конце критической секции (например, чтобы разблокировать спин-блокировку) должно обеспечить, чтобы загрузки / хранилища внутри критической секции не появлялись позже, но не должна задерживаться позжезагружается до тех пор, пока lock=0
не станет глобально видимым.
У Джеффа Прешинга тоже есть статья на эту тему: Семантика получения и выпуска
"полный"Терминология «частичного» барьера обычно не используется для ограничения одностороннего переупорядочения в хранилище релизов или в приобретении.Фактический релиз fence (в C ++ 11, std::atomic_thread_fence(std::memory_order_release)
) блокирует переупорядочение хранилищ в обоих направлениях, в отличие от хранилища релизов для определенного объекта.
Это тонкое различие вызывало путаницу в прошлом (даже среди экспертов!).У Джеффа Прешинга есть еще одна отличная статья, объясняющая это: Заборы для получения и выпуска не работают так, как вы ожидаете .
Вы правы, что односторонний барьер, который не былпривязать к магазину или грузу было бы не очень полезно;вот почему такой вещи не существует.: P Это может изменить порядок неограниченного расстояния в одном направлении и оставить все операции для изменения порядка друг с другом.
Что именно делает atomic_thread_fence(memory_order_release)
?
C11 ( n1570 Раздел 7.17.4 Ограждения ) определяет его только с точки зрения создания отношения синхронизация-с забором-загрузкой или приобретением-забором, когда разделительный-забор используется перед атомным хранилищем (расслаблено)или иным образом) к тому же объекту нагрузка получает доступ.(C ++ 11 имеет в основном то же определение, но обсуждение с @EOF в комментариях привело к версии C11.)
Это определение с точки зрения чистого эффекта, а не механизма его достижения, не напрямую скажите нам, что он делает или не позволяет.Например, подраздел 3 гласит:
3) Ограничение освобождения A синхронизируется с атомарной операцией B, которая выполняет операцию получения на атомарном объекте M, если существует атомарная операция X, такая, что A секвенируется передX, X изменяет M, а B читает значение, записанное X, или значение, записанное любым побочным эффектом в гипотетической последовательности деблокирования, X будет начинаться, если бы это была операция деблокирования
Так в потоке записи, это говорит о коде, как это:
stuff // including any non-atomic loads/stores
atomic_thread_fence(mo_release) // A
M=X // X
// threads that see load(M, acquire) == X also see stuff
Синхронизация означает, что потоки, которые видят значение из M=X
(прямо или косвенно через последовательность выпуска), также видят все stuff
и читают неатомарные переменные без Data Race UB.
Это позволяет нам сказать что-то о том, что разрешено / не разрешено:
Это двусторонний барьер для атомных магазинов .Они не могут пересекать его в любом направлении, поэтому расположение барьера в порядке памяти этого потока ограничено атомными хранилищами до и после.Любое более раннее хранилище может быть частью stuff
для некоторых M
, любое более позднее хранилище может быть M
, с которым синхронизируется начальная загрузка (или загрузка + приобретение-забор).
Это односторонний барьер для атомных нагрузок: более ранние должны оставаться перед барьером, но более поздние могут двигаться выше барьера.M=X
может быть только магазином (или частью магазина RMW).
Это односторонний барьер для неатомных нагрузок / хранилищ : неатомарные хранилища могут бытьчасть stuff
, но не может быть X
, потому что они не атомарные.Можно допустить, чтобы последующие загрузки / сохранения в этой теме отображались другим потокам до M=X
.(Если неатомарная переменная изменена до и после барьера, то ничто не сможет безопасно прочитать ее даже после синхронизации с этим барьером, если только у читателя не будет способа остановить продолжение потока и создать Data Race UBТаким образом, компилятор может и должен переупорядочить foo=1; fence(release); foo=2;
в foo=2; fence(release);
, устраняя мертвое хранилище foo=1
. Но понижение foo=1
после барьера законно только по технической причине, что ничто не может отличить без UB.)
В качестве детали реализации, ограничитель релиза C11 может быть сильнее этого (например, двухсторонний барьер для большего количества видов переупорядочения во время компиляции), но не слабее.На некоторых архитектурах (например, ARM) единственной достаточно сильной опцией может быть инструкция asm с полным барьером.А что касается ограничений на переупорядочение во время компиляции, компилятор может не разрешить эти односторонние переупорядочения только для упрощения реализации.
В основном, эта комбинированная двухсторонняя / односторонняя природа имеет значение только для переупорядочение во время компиляции .Процессоры не делают различий между атомарными и неатомарными хранилищами.Неатомарная - это всегда та же команда asm, что и расслабленная атомарная (для объектов, которые помещаются в один регистр).
Инструкции барьера ЦП, заставляющие ядро ждать, пока более ранние операции не станут видны глобально, обычно являются двусторонними барьерами;они определены в терминах операций, которые становятся глобально видимыми в согласованном представлении памяти, совместно используемой всеми ядрами, а не в стиле C / C ++ 11 для создания отношений синхронизация с.(Помните, что операции могут потенциально стать видимыми для некоторых других потоков, прежде чем они станут глобально видимыми для всех потоков: Будут ли всегда видны две атомарные записи в разные места в разных потокахтот же порядок в других потоках? . Но при наличии только барьеров, препятствующих переупорядочению в физическом ядре, последовательная согласованность может быть восстановлена.)
Для ограждения релиза C ++ 11 нужны барьеры LoadStore + StoreStore, ноне LoadLoad.Процессор, который позволяет вам получить только эти 2, но не все 3 «дешевых» барьера, позволил бы переупорядочивать нагрузки в одном направлении через команду барьера, в то же время блокируя хранилища в обоих направлениях.
Фактически слабо упорядоченный SPARCкак это, и использует LoadStore и т. д. терминологию (вот где Джефф Прешинг взял терминологию для своих статей).http://blog.forecode.com/2010/01/29/barriers-to-understanding-memory-barriers/ показывает, как они используются.(В более поздних версиях SPARC используется модель памяти TSO (Total Store Order). Я думаю, что это похоже на x86, где аппаратное обеспечение создает иллюзию операций с памятью, происходящих в порядке программ, за исключением переупорядочения StoreLoad.)