Модель памяти С++ - содержит ли этот пример гонку данных?

Я читал Bjarne Stroustrup С++ 11 FAQ, и мне трудно понять пример в модель памяти.

Он дает следующий фрагмент кода:

// start with x==0 and y==0
if (x) y = 1; // thread 1
if (y) x = 1; // thread 2

Часто задаваемые вопросы говорят, что здесь нет гонки данных. Я не понимаю. Местоположение памяти x считывается потоком 1 и записывается потоком 2 без какой-либо синхронизации (и то же самое относится к y). Это два доступа, одним из которых является запись. Разве это не определение гонки данных?

Кроме того, в нем говорится, что "каждый текущий компилятор С++ (который я знаю) дает один правильный ответ". Что это за правильный ответ? Невозможно, чтобы ответ зависел в зависимости от того, происходит ли сравнение нитей до или после записи другого потока (или если другая запись потока даже видна для потока чтения)?

Ответ 1

// start with x==0 and y==0
if (x) y = 1; // thread 1
if (y) x = 1; // thread 2

Так как ни x, ни y не истинно, другое значение не будет равно true. Независимо от порядка выполнения инструкций (правильный) результат всегда остается x, а y остается 0.

Ответ 2

Местоположение памяти x... записано в поток 2

Это правда? Почему вы так говорите?

Если y равно 0, то x не записывается в поток 2. И y начинается 0. Точно так же x не может быть отличным от нуля, если только как-то y не равно нулю "до" поток 1 работает, и этого не может быть. Общий смысл здесь заключается в том, что условные записи, которые не выполняются, не приводят к гонке данных.

Это нетривиальный факт модели памяти, хотя, потому что компилятор, который не знает о потоке, будет разрешен (если y не является volatile), чтобы преобразовать код if (x) y = 1; в int tmp = y; y = 1; if (!x) y = tmp;. Тогда будет гонка данных. Я не могу себе представить, почему он хотел бы сделать это точное преобразование, но это не имеет значения, дело в том, что оптимизаторы для не-потоковых сред могут делать то, что нарушает модель с потоковой памятью. Поэтому, когда Stroustrup говорит, что каждый компилятор, который он знает, дает правильный ответ (прямо под моделью потоковой передачи С++ 11, то есть), что нетривиальное утверждение о готовности этих компиляторов для потоковой передачи С++ 11.

Более реалистичное преобразование if (x) y = 1 было бы y = x ? 1 : y;. Я считаю, что это приведет к гонке данных в вашем примере и что в стандарте для назначения y = y нет специального режима, что делает его безопасным для выполнения без изменений в отношении чтения y в другом потоке. Возможно, вам будет трудно представить себе аппаратное обеспечение, на котором оно не работает, и в любом случае я могу ошибаться, поэтому я использовал другой пример выше, который менее реалистичен, но имеет вопиющую гонку данных.

Ответ 3

Должен быть полный порядок записи, поскольку ни один поток не может записывать в переменную x или y, пока какой-либо другой поток не написал первую переменную 1. Другими словами, у вас есть в основном три разных сценария:

  • поток 1 получает запись в y, потому что x был записан в предыдущую точку перед оператором if, а затем, если поток 2 приходит позже, он записывает в x то же значение 1 и не меняет прежнее значение 1.
  • поток 2 получает запись в x, потому что y был изменен в какой-то момент перед оператором if, а затем поток 1 будет записываться в y, если наступит позднее то же значение 1.
  • Если есть только два потока, то операторы if перескакивают, потому что x и y остаются 0.

Ответ 4

Ни одна из записей не происходит, поэтому нет расы. И x и y остаются равными нулю.

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

Ответ 5

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