Кто-нибудь может объяснить это утверждение:
shared variables
x = 0, y = 0
Core 1 Core 2
x = 1; y = 1;
r1 = y; r2 = x;
Как возможно иметь r1 == 0
и r2 == 0
на процессорах x86?
Источник "Язык параллелизма", Бартош Милевски.
Кто-нибудь может объяснить это утверждение:
shared variables
x = 0, y = 0
Core 1 Core 2
x = 1; y = 1;
r1 = y; r2 = x;
Как возможно иметь r1 == 0
и r2 == 0
на процессорах x86?
Источник "Язык параллелизма", Бартош Милевски.
Проблема может возникнуть из-за оптимизации, включающей изменение порядка инструкций. Другими словами, оба процессора могут назначать 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 (где инструкции чтения и записи всегда выполнялись в том порядке, в котором они появлялись в поток команд).
В руководстве описываются многие гарантии упорядочения модели памяти упорядочения процессора (например, Загрузки не переупорядочиваются с другими загрузками, Хранилища не переупорядочиваются с другими хранилищами, Хранилища не переупорядочивается со старыми загрузками и т. д.), но также описывает разрешенное правило переупорядочивания, которое вызывает состояние гонки в посте OP:
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 Stores Not Reordered with Early Load), что означает, что эти инструкции никогда не будут переупорядочены в отдельных ядрах.
Чтобы сравнить это с различными архитектурами, ознакомьтесь со статьей: Упорядочивание памяти в современных микропроцессорах< /а>. Вы можете видеть, что Itanium (IA-64) переупорядочивает даже больше, чем архитектура IA-32:
sigprocmask
.
- person R.. GitHub STOP HELPING ICE; 08.07.2011
На процессорах с более слабой моделью согласованности памяти (таких как SPARC, PowerPC, Itanium, ARM и т. д.) указанное выше условие может иметь место из-за отсутствия принудительной когерентности кэша при записи без явной инструкции барьера памяти. Таким образом, в основном Core1
видит запись на x
до y
, а Core2
видит запись на y
до x
. В этом случае не требуется полная инструкция ограждения ... в основном вам нужно будет только применить семантику записи или освобождения с этим сценарием, чтобы все записи были зафиксированы и видны всем процессорам до того, как произойдет чтение тех переменных, которые были написано на. Процессорные архитектуры со строгой моделью согласованности памяти, такие как x86, обычно делают это ненужным, но, как указывает Гру, сам компилятор может изменить порядок операций. Вы можете использовать ключевое слово volatile
в C и C++, чтобы предотвратить переупорядочивание операций компилятором в данном потоке. Это не означает, что volatile
создаст потокобезопасный код, который управляет видимостью операций чтения и записи между потоками... для этого потребуется барьер памяти. Таким образом, хотя использование volatile
может по-прежнему создавать небезопасный многопоточный код, внутри данного потока будет обеспечиваться последовательная согласованность на уровне соблюдаемого машинного кода.
volatile
не предотвратил переупорядочивание компилятора в заданном потоке, то это было бы абсолютно бессмысленно для использования в отображенном на память вводе-выводе... компилятор все равно переупорядочил бы операции чтения и записи, и вы бы получили всевозможные неопределенное поведение оборудования. Ключевое слово volatile
не предотвращает переупорядочивание инструкций ЦП и не обеспечивает видимость операций чтения и записи между потоками... что требует барьера памяти... но не позволяет самому компилятору переупорядочивать операции в данном потоке как определено в исходном коде.
- person Jason; 08.07.2011
Проблема в том, что ни один из потоков не обеспечивает какого-либо порядка между своими двумя операторами, потому что они не зависят друг от друга.
Компилятор знает, что x и y не являются псевдонимами, поэтому не требуется упорядочивать операции.
ЦП знает, что x и y не являются псевдонимами, поэтому он может изменить их порядок для ускорения. Хорошим примером того, когда это происходит, является случай, когда ЦП обнаруживает возможность для объединения записей. Он может объединять одну запись с другой, если может сделать это, не нарушая своей модели согласованности.
Взаимная зависимость выглядит странно, но на самом деле она ничем не отличается от любого другого состояния гонки. Непосредственно писать многопоточный код с общей памятью довольно сложно, и поэтому были разработаны параллельные языки и параллельные среды передачи сообщений, чтобы изолировать параллельные опасности для небольшого ядра и устранить опасности из самих приложений.
mfence
для демонстрации переупорядочения памяти с объяснением того, почему это разрешено. В C/C++ это просто UB, если они неatomic
; если они есть, то переупорядочение может произойти только сmemory_order_release
или слабее. - person Peter Cordes   schedule 26.03.2020