Состояние гонки на x86

Может ли кто-нибудь объяснить это утверждение:

shared variables
x = 0, y = 0

Core 1       Core 2
x = 1;       y = 1;
r1 = y;      r2 = x;

Как можно иметь r1 == 0 а также r2 == 0 на процессорах x86?

Источник "Язык параллелизма" Бартоша Милевского.

3 ответа

Решение

Проблема может возникнуть из-за оптимизаций, связанных с переупорядочением инструкций. Другими словами, оба процессора могут назначать r1 а также r2 перед назначением переменных x а также y, если они обнаружат, что это даст лучшую производительность. Эту проблему можно решить, добавив барьер памяти, который будет обеспечивать ограничение порядка.

Чтобы процитировать слайд-шоу, которое вы упомянули в своем посте:

Современные многоядерные / языки нарушают последовательную согласованность.

Что касается архитектуры x86, лучшим ресурсом для чтения является Руководство разработчика программного обеспечения для архитектуры Intel® 64 и IA-32 (глава 8.2. Упорядочение памяти). Разделы 8.2.1 и 8.2.2 описывают упорядочение памяти, реализованное процессорами Intel486, Pentium, Intel Core 2 Duo, Intel Atom, Intel Core Duo, Pentium 4, Intel Xeon и P6: модель памяти, называемая упорядочением процессора, как в отличие от программного упорядочения (строгого упорядочения) более старой архитектуры Intel386 (где инструкции чтения и записи всегда выполнялись в том порядке, в котором они появлялись в потоке команд).

В руководстве описываются многие гарантии упорядочения модели памяти упорядочения процессора (например, Нагрузки не переупорядочиваются с другими нагрузками, Хранилища не переупорядочиваются с другими хранилищами, Хранилища не переупорядочиваются со старыми нагрузками и т. Д.), Но также описывается разрешенное правило переупорядочения. что приводит к состоянию гонки в должности ОП:

8.2.3.4 Грузы могут быть переупорядочены с более ранними магазинами в разные места

С другой стороны, если первоначальный порядок инструкций был изменен:

shared variables
x = 0, y = 0

Core 1       Core 2
r1 = y;      r2 = x;
x = 1;       y = 1;

В этом случае процессор гарантирует, что r1 = 1 а также r2 = 1 Ситуация недопустима (из-за 8.2.3.3. Магазины не переупорядочиваются с гарантией более ранней загрузки), что означает, что эти инструкции никогда не будут переупорядочены в отдельных ядрах.

Чтобы сравнить это с различными архитектурами, ознакомьтесь с этой статьей: Упорядочение памяти в современных микропроцессорах ( это изображение специально). Вы можете видеть, что Itanium (IA-64) делает еще больше переупорядочения, чем архитектура IA-32.

На процессорах с более слабой моделью согласованности памяти (таких как SPARC, PowerPC, Itanium, ARM и т. Д.) Указанное выше условие может иметь место из-за отсутствия принудительной когерентности кэша при записи без явной инструкции барьера памяти. Так в основном Core1 видит запись на x до y, в то время как Core2 видит запись на y до x, В этом случае не требуется полная инструкция по ограничению... в основном вам нужно будет применять семантику записи или освобождения в этом сценарии, чтобы все записи фиксировались и были видимы для всех процессоров, прежде чем произойдут чтения по тем переменным, которые были написано Архитектуры процессоров с такими сильными моделями согласованности памяти, как x86, обычно делают это ненужным, но, как отмечает Гро, сам компилятор может переупорядочивать операции. Вы можете использовать volatile ключевое слово в C и C++ для предотвращения переупорядочения операций компилятором в заданном потоке. Это не значит, что volatile создаст потокобезопасный код, который управляет видимостью операций чтения и записи между потоками... для этого потребуется барьер памяти. Так что пока использование volatile может по-прежнему создавать небезопасный многопоточный код, в данном потоке он будет обеспечивать последовательную согласованность на уровне машинного кода.

Вот почему некоторые говорят: темы, считающиеся вредными

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

  • Компилятор знает, что x и y не являются псевдонимами, и поэтому не требуется упорядочивать операции.

  • Процессор знает, что x и y не являются псевдонимами, поэтому он может переупорядочить их по скорости. Хороший пример того, когда это происходит, - когда процессор обнаруживает возможность объединения записи. Он может объединить одну запись с другой, если он может сделать это, не нарушая свою модель когерентности.

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

Другие вопросы по тегам