Является ли объяснение расслабленного порядка ошибочным в сравнении?


13

В документации std::memory_orderна cppreference.com есть пример непринужденного заказа:

Расслабленный заказ

Маркированные атомарные операции memory_order_relaxedне являются операциями синхронизации; они не навязывают порядок между одновременными обращениями к памяти. Они гарантируют только атомарность и согласованность порядка модификации.

Например, с х и у изначально ноль,

// Thread 1:
r1 = y.load(std::memory_order_relaxed); // A
x.store(r1, std::memory_order_relaxed); // B
// Thread 2:
r2 = x.load(std::memory_order_relaxed); // C
y.store(42, std::memory_order_relaxed); // D

разрешено производить r1 == r2 == 42, потому что, хотя A секвенируется перед B в потоке 1, а C секвенируется перед D в потоке 2, ничто не мешает D появляться перед A в порядке изменения y, а B - появляются перед C в порядке модификации x. Побочный эффект D на y может быть виден нагрузке A в потоке 1, в то время как побочный эффект B на x может быть виден нагрузке C в потоке 2. В частности, это может произойти, если D завершен до C в поток 2, либо из-за переупорядочения компилятора, либо во время выполнения.

он говорит: «C секвенируется перед D в потоке 2».

Согласно определению секвенированного до, которое можно найти в Порядке оценки , если A секвенируется до B, тогда оценка A будет завершена до того, как начнется оценка B. Поскольку C секвенируется перед D в потоке 2, C должен быть завершен до начала D, следовательно, условие условия последнего предложения снимка никогда не будет выполнено.


Ваш вопрос конкретно о C ++ 11?
любопытный парень

нет, это также относится к c ++ 14,17. Я знаю, что и компилятор, и процессор могут переупорядочить C с помощью D. Но если переупорядочение произойдет, C не может быть завершено до начала D. Таким образом, я думаю, что в предложении «неправильное использование терминологии есть выражение« A секвенируется перед B в потоке 1, а C секвенируется перед D в потоке 2 ». Точнее сказать: «В коде A РАЗМЕЩЕНО ПЕРЕД B в потоке 1, а C РАЗМЕЩЕНО ДО D в потоке 2». Цель этого вопроса - подтвердить эту мысль
abigaile

Ничто не определено в терминах «переупорядочения».
любопытный парень

Ответы:


13

Я считаю, что cppreference это правильно. Я думаю, что это сводится к правилу «как будто» [intro.execution] / 1 . Компиляторы обязаны воспроизводить только наблюдаемое поведение программы, описанное вашим кодом. Секвенировал-прежде , чем отношение только устанавливается между оценками с точки зрения потока , в котором эти оценки выполняются [intro.execution] / 15 . Это означает, что когда две оценки, последовательно расположенные одна за другой, появляются где-то в каком-то потоке, код, фактически выполняющийся в этом потоке, должен вести себя так, как будто то, что делает первая оценка, действительно влияет на то, что делает вторая оценка. Например

int x = 0;
x = 42;
std::cout << x;

must print 42. Однако компилятору на самом деле не нужно сохранять значение 42 в объекте xперед тем, как прочитать значение из этого объекта, чтобы напечатать его. Он также может помнить, что последнее значение, которое должно быть сохранено, xбыло 42, а затем просто печатать значение 42 непосредственно перед выполнением фактического сохранения значения 42 в x. Фактически, если xэто локальная переменная, она также может просто отслеживать, какое значение этой переменной было в последний раз присвоено в любой точке, и никогда даже не создавать объект или фактически хранить значение 42. Поток не может определить разницу. Поведение всегда будет таким, как если бы существовала переменная, и как если бы значение 42 действительно сохранялось в объекте x ранеезагружается из этого объекта. Но это не значит, что сгенерированный машинный код должен хранить и загружать что-либо где-либо. Все, что требуется, - это то, что наблюдаемое поведение сгенерированного машинного кода неотличимо от того, что было бы, если бы все эти вещи действительно происходили.

Если мы посмотрим на

r2 = x.load(std::memory_order_relaxed); // C
y.store(42, std::memory_order_relaxed); // D

тогда да, C секвенируется перед D. Но если смотреть из этого потока изолированно, ничто из того, что делает C, не влияет на результат D. И ничто из того, что делает D, не изменяет результат C. Единственный способ, которым один может повлиять на другой, - это как косвенное следствие того, что происходит в другом потоке. Однако, указав std::memory_order_relaxed, вы явно заявиличто порядок, в котором загрузка и хранение наблюдаются другим потоком, не имеет значения. Поскольку ни один другой поток не может наблюдать за загрузкой и хранением в каком-либо определенном порядке, другой поток не может ничего сделать, чтобы заставить C и D последовательно влиять друг на друга. Таким образом, порядок, в котором загрузка и хранение фактически выполняются, не имеет значения. Таким образом, компилятор может изменить их порядок. И, как упомянуто в пояснении под этим примером, если сохранение из D выполняется до загрузки из C, тогда r1 == r2 == 42 действительно может произойти ...


По сути, стандарт гласит, что C должен произойти до D , но компилятор считает, что невозможно доказать, произошел ли C или D следующим, и, в силу правила «как будто», переупорядочивает их в любом случае, верно?
Fureeish

4
@ Fureeish Нет. C должно произойти до D, насколько может судить нить, по которой они происходят. Наблюдение из другого контекста может не соответствовать этой точке зрения.
Дедупликатор


1
@curiousguy Майкл опубликовал подробное объяснение этого, а также ссылки на соответствующие главы в стандарте.
Гонки легкости на орбите

2
@curiousguy Стандарт делает ярлык один из его положений «как если бы правила» в примечании: «Это положение иногда называют„как если бы“правила» intro.execution
Caleth

1

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

Предположим, например, что у человека есть три следующих события:

  • хранить от 1 до p1
  • загрузить р2 в темп
  • хранить от 2 до p3

и чтение p2 независимо упорядочивается после записи p1 и перед записью p3, но нет особого порядка, в котором участвуют как p1, так и p3. В зависимости от того, что сделано с p2, компилятору может быть нецелесообразно отложить p1 после p3 и все же достичь требуемой семантики с p2. Предположим, однако, что компилятор знал, что приведенный выше код был частью большой последовательности:

  • сохранить от 1 до p2 [упорядочено до загрузки p2]
  • [сделать выше]
  • сохранить 3 в p1 [последовательно после другого хранилища в p1]

В этом случае он может определить, что он может переупорядочить хранилище в p1 после вышеприведенного кода и объединить его со следующим хранилищем, что приведет к коду, который записывает p3 без записи p1:

  • установить темп 1
  • сохранить темп до p2
  • хранить от 2 до p3
  • хранить от 3 до p1

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


1

Если есть два оператора, компилятор будет генерировать код в последовательном порядке, поэтому код для первого будет размещен перед вторым. Но внутри процессора есть конвейеры, и они могут выполнять сборочные операции параллельно. Оператор C является инструкцией загрузки. Во время извлечения памяти конвейер будет обрабатывать следующие несколько инструкций, и, учитывая, что они не зависят от инструкции загрузки, они могут в конечном итоге быть выполнены до завершения C (например, данные для D находились в кеше, C в основной памяти).

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


-10

Все, что вы думаете, одинаково справедливо. Стандарт не говорит, что выполняется последовательно, что нет, и как это можно перепутать .

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.