Модель памяти С++ - содержит ли этот пример гонку данных?
Я читал 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
Модель памяти задает поддерживаемый размер областей кода и данных. Перед сопоставлением исходного кода ссылки нам нужно указать модель памяти, которую он может установить для ограничения размера данных и кода.