В стандарте ISO C ++ нет проблем; он не различает guish время компиляции и переупорядочение во время выполнения, и код все еще должен выполнить , как если бы выполнялся в исходном порядке на абстрактной машине C ++. Таким образом, эффекты m2.test_and_set(std::memory_order_acquire)
попытки захватить 2-ю блокировку могут стать видимыми для других потоков, в то же время удерживая первый (т. Е. До m1.reset
), но неудача там не может помешать освобождению m1
.
Единственный способ, которым у нас возникнет проблема, - это если время компиляции переупорядочить, прибавив этот порядок в asm для некоторой машины, так что m2
повторная попытка блокировки l oop должна была завершиться до того, как фактически освобождая m1
.
Кроме того, ISO C ++ определяет порядок только в терминах синхронизаций и того, что можно увидеть, а не в терминах операций re , относящихся к некоторому новому порядку. , Это будет означать, что существует какой-то порядок. Ни один порядок, с которым могут согласиться несколько потоков, даже не гарантированно существует для отдельных объектов, если только вы не используете операции seq_cst. (И порядок модификации для каждого объекта в отдельности гарантированно существует.)
Односторонняя барьерная модель операций захвата и выпуска (как на диаграмме в https://preshing.com/20120913/acquire-and-release-semantics) представляет собой удобный способ думать о вещах и соответствует реальности для чистых загрузок и чистых хранилищ, например, на x86 и AArch64. Но что касается языковой адвокатуры, это не то, как стандарт ISO C ++ определяет вещи.
Вы переупорядочиваете целую повторную попытку l oop, а не просто одно приобретение
Переупорядочение операции atomic
через длительный l oop является теоретической проблемой, допускаемой стандартом C ++. P0062R1: Когда компиляторы должны оптимизировать атомарность? указывает на то, что задержка магазина до долгого времени l oop технически разрешена формулировкой стандарта 1.10p28:
An реализация должна гарантировать, что последнее значение (в порядке изменения), назначенное атомом c или операцией синхронизации, станет видимым для всех других потоков за конечный период времени .
Но потенциально бесконечное l oop может нарушить это, не будучи конечным, например, в случае тупика, поэтому компиляторы не должны этого делать.
Это не "просто" качество - Внедрение вопроса. успешная блокировка мьютекса - это операция получения, но вы должны , а не смотреть на попытку l oop как одну операцию получения. Любой здравомыслящий компилятор не будет.
(Классифицированный пример того, что агрессивная атомная оптимизация может сломать, - это индикатор выполнения, где компилятор отбирает все расслабленные хранилища из всех oop, а затем сворачивает все мертвые хранилища в одном окончательном хранилище на 100%. См. также этот вопрос и ответ - текущие компиляторы этого не делают, и в основном обрабатывают atomic
как volatile atomic
до тех пор, пока C ++ не решит проблему предоставления программистам возможности сообщите компилятору, когда атомика может / не может быть оптимизирована безопасно.)