Не используйте этот код на практике, используйте std::atomic<bool>
с memory_order_release
и acquire
, чтобы получить тот же asm code-gen (но без ненужные lfence и sfence)
Но да, это выглядит безопасным , для компиляторов, которые определяют поведение volatile
таким, что UB гонки данных на volatile bool flag
isn это не проблема. Это относится к компиляторам, таким как G CC, которые могут компилировать ядро Linux (которое катит свою собственную атомику, используя volatile
, как вы делаете).
ISO C ++ строго не требует этого Например, гипотетическая реализация может существовать на машине без согласованной общей памяти, поэтому хранилища Atomi c потребуют явной очистки. Но на практике таких реализаций нет. (Однако существуют некоторые встроенные системы, в которых volatile
магазины используют разные или дополнительные инструкции для работы MMIO.)
Барьер перед магазином делает его магазином релиза, а барьер после загрузки делает это приобретением нагрузки. https://preshing.com/20120913/acquire-and-release-semantics/. Происходит до того, как может быть установлено только хранилище релизов, видимое загрузкой захвата.
Модель памяти x86 asm уже запрещает все переупорядочения, кроме StoreLoad, поэтому блокировать нужно только переупорядочение во время компиляции , Это скомпилирует в asm, это то же самое, что вы получите от использования std::atomic<bool>
с mo_release
и mo_acquire
, за исключением тех неэффективных инструкций LFENCE и SFENCE.
C ++ Как выпуск -and-receive, достигнутый на x86 только с использованием MOV? объясняет, почему модель памяти x86 asm по крайней мере так же сильна, как acq_rel.
Инструкции sfence
и lfence
внутри операторов asm совершенно не имеет значения , только барьерная часть asm("" ::: "memory")
необходима. https://preshing.com/20120625/memory-ordering-at-compile-time/. Переупорядочение во время компиляции должно учитывать только модель памяти C ++, но то, что выбирает компилятор, затем ограничивается моделью памяти x86. (Программный порядок + буфер хранения с пересылкой в хранилище = немного сильнее, чем acq_rel)
(Оператор GNU C asm
без выходных операндов неявно изменчив, поэтому я опускаю явный volatile
. )
(Если вы не пытаетесь синхронизировать хранилища NT? Если это так, вам нужно только sfence
, а не lfence
.) Делает ли модель памяти Intel избыточность SFENCE и LFENCE? да. Memset, который внутренне использует хранилища NT, будет сам использовать sfence
, чтобы сделать себя совместимым со стандартным стандартом C ++ atomics / ordering -> asm mapping , используемым в x86. Если вы используете другое отображение (например, свободно использующее хранилища NT без sfence), теоретически вы можете разбить критические секции мьютекса, если вы тоже не сверните свои собственные мьютексы. (На практике в большинстве реализаций мьютекса при взятии и выпуске используется инструкция lock
ed, которая является полным барьером.)
Пустой оператор asm с клоббером памяти является своего рода броском - ваш собственный эквивалент atomic_thread_fence(std::memory_order_acquire_release)
из-за модели памяти x86. atomic_thread_fence(acq_rel)
скомпилирует ноль asm-инструкций, просто заблокировав переупорядочение во время компиляции.
Только seq_cst thread fence должен отправлять любые asm-инструкции для flu sh буфера хранилища и ждать, пока это произойдет, прежде чем позже. грузы. он же полный барьер (например, mfence
или lock
ed инструкция, например, lock add qword ptr [rsp], 0
).
Не катите свою собственную атомарность, используя volatile
и встроенный asm
Да, вы можете, и я надеюсь, что вы просто просили понять, как все работает.
В итоге вы сделали что-то гораздо менее эффективное, чем нужно, потому что вы использовали lfence
(вне барьер выполнения заказа, который по сути бесполезен для упорядочивания памяти), а не просто барьер компилятора. И ненужные sfence
.
См. Когда я должен использовать _mm_sfence _mm_lfence и _mm_mfence для в основном той же проблемы, но с использованием встроенных функций вместо встроенного asm. Обычно вам нужно только _mm_sfence()
после встроенных функций NT-хранилища, и вы должны оставить mfence
до компилятора с std::atomic
.
Когда использовать volatile с многопоточностью? - обычно никогда; используйте std::atomic
с mo_relaxed
вместо volatile
.