Я часто нахожу, что эти термины используются в контексте параллельного программирования. Это одно и то же или разные?
Я часто нахожу, что эти термины используются в контексте параллельного программирования. Это одно и то же или разные?
Ответы:
Нет, это не одно и то же. Они не являются частью друг друга. Они также не являются ни необходимым, ни достаточным условием друг для друга.
Определение гонки данных довольно четкое, и поэтому ее обнаружение можно автоматизировать. Гонка данных возникает, когда 2 инструкции из разных потоков обращаются к одному и тому же участку памяти, по крайней мере, один из этих обращений является записью, и нет синхронизации, которая предписывала бы какой-либо конкретный порядок между этими доступами.
Состояние гонки - это семантическая ошибка. Это ошибка, возникающая во времени или порядке событий, которая приводит к ошибочному поведению программы. Многие состояния гонки могут быть вызваны гонкой данных, но это не обязательно.
Рассмотрим следующий простой пример, где x - общая переменная:
Thread 1 Thread 2
lock(l) lock(l)
x=1 x=2
unlock(l) unlock(l)
В этом примере записи в x из потоков 1 и 2 защищены блокировками, поэтому они всегда происходят в некотором порядке, обеспечиваемом порядком, в котором блокировки устанавливаются во время выполнения. То есть атомарность записи не может быть нарушена; всегда существует связь между двумя операциями записи при любом выполнении. Мы просто не можем знать априори, какая запись происходит раньше другой.
Между записями нет фиксированного порядка, потому что блокировки не могут этого обеспечить. Если корректность программ нарушена, например, когда за записью в x потоком 2 следует запись в x в потоке 1, мы говорим, что существует состояние гонки, хотя технически гонки данных нет.
Гораздо полезнее обнаруживать состояния гонки, чем гонки данных; однако этого также очень трудно добиться.
Построение обратного примера также тривиально. Это сообщение в блоге также очень хорошо объясняет разницу на простом примере банковской транзакции.
Согласно Википедии, термин «состояние гонки» используется со времен появления первых электронных логических вентилей. В контексте Java состояние гонки может относиться к любому ресурсу, например к файлу, сетевому соединению, потоку из пула потоков и т. Д.
Термин «гонка данных» лучше всего зарезервировать для его конкретного значения, определенного JLS .
Самый интересный случай - это состояние гонки, которое очень похоже на гонку данных, но все же не так, как в этом простом примере:
class Race {
static volatile int i;
static int uniqueInt() { return i++; }
}
Поскольку i
является изменчивым, гонка данных отсутствует; однако с точки зрения корректности программы существует состояние гонки из-за неатомарности двух операций: чтения i
и записи i+1
. Несколько потоков могут получать одно и то же значение из uniqueInt
.
data race
самом деле означает в JLS?
Нет, они разные, и ни один из них не является подмножеством одного или наоборот.
Термин «состояние гонки» часто путают со связанным термином «гонка данных», который возникает, когда синхронизация не используется для координации всего доступа к общему нефинальному полю. Вы рискуете получить гонку данных всякий раз, когда поток записывает переменную, которая может быть прочитана другим потоком, или считывает переменную, которая могла быть последней записана другим потоком, если оба потока не используют синхронизацию; код с гонками данных не имеет полезной определенной семантики в рамках модели памяти Java. Не все состояния гонки являются гонками данных, и не все гонки данных являются состояниями гонки, но оба они могут вызвать непредсказуемый сбой параллельных программ.
Взято из превосходной книги - Java Concurrency in Practice от Джошуа Блоха и Ко.
TL; DR: различие между гонкой данных и состоянием гонки зависит от характера формулировки проблемы и от того, где провести границу между неопределенным поведением и четко определенным, но неопределенным поведением. Текущее различие условно и лучше всего отражает интерфейс между архитектором процессора и языком программирования.
1. Семантика
Гонка данных, в частности, относится к несинхронизированным конфликтующим «доступам к памяти» (или действиям, или операциям) к одной и той же области памяти. Если в доступе к памяти нет конфликта, но все еще существует неопределенное поведение, вызванное упорядочением операций, это состояние гонки.
Обратите внимание, что «доступ к памяти» здесь имеет особое значение. Они относятся к «чистой» загрузке памяти или действиям сохранения без какой-либо дополнительной семантики. Например, хранилище памяти из одного потока не обязательно (обязательно) знает, сколько времени требуется, чтобы данные были записаны в память и, наконец, распространяются в другой поток. В качестве другого примера, сохранение в памяти одного места перед другим сохранением в другом месте одним и тем же потоком не (обязательно) гарантирует, что первые данные, записанные в память, будут впереди вторых. В результате порядок этих обращений к чистой памяти не (обязательно) может быть «обоснован» , и все может произойти, если иное не определено четко.
Когда «доступы к памяти» четко определены с точки зрения упорядочения посредством синхронизации, дополнительная семантика может гарантировать, что даже если время доступа к памяти не определено, их порядок можно «обосновать» посредством синхронизации. Обратите внимание, хотя порядок между обращениями к памяти можно обосновать, они не обязательно являются определяющими, отсюда и состояние гонки.
2. Почему разница?
Но если порядок все еще не определен в состоянии гонки, зачем вообще отличать его от гонки данных? Причина скорее практическая, чем теоретическая. Это потому, что существует различие в интерфейсе между языком программирования и архитектурой процессора.
Команда загрузки / сохранения памяти в современной архитектуре обычно реализуется как «чистый» доступ к памяти из-за природы неупорядоченного конвейера, предположений, многоуровневого кеширования, взаимодействия ЦП и ОЗУ, особенно многоядерных и т. Д. Существует множество факторов, которые приводят к неопределенности в выборе времени и порядка. Принуждение к упорядочиванию каждой инструкции памяти влечет за собой огромные штрафы, особенно в конструкции процессора, поддерживающей многоядерность. Таким образом, семантика упорядочивания снабжена дополнительными инструкциями, такими как различные барьеры (или ограждения).
Гонка данных - это ситуация выполнения инструкций процессора без дополнительных ограждений, помогающих обосновать порядок конфликтующих обращений к памяти. Результат не только неопределенный, но также, возможно, очень странный, например, две записи в одно и то же место слова разными потоками могут произойти с каждой записывающей половиной слова или могут работать только с их локально кэшированными значениями. - Это неопределенное поведение с точки зрения программиста. Но они (обычно) хорошо определены с точки зрения архитектора процессора.
Программисты должны иметь способ обосновать выполнение своего кода. Гонка за данными - это то, что они не могут иметь смысла, поэтому всегда следует избегать (обычно). Вот почему спецификации языка на достаточно низком уровне обычно определяют гонку данных как неопределенное поведение, отличное от четко определенного поведения памяти в состоянии гонки.
3. Модели языковой памяти
Различные процессоры могут иметь разное поведение доступа к памяти, т. Е. Модель памяти процессора. Программистам неудобно изучать модель памяти каждого современного процессора, а затем разрабатывать программы, которые могут извлечь из них пользу. Желательно, чтобы язык мог определять модель памяти, чтобы программы на этом языке всегда вели себя так, как ожидалось, как это определяет модель памяти. Вот почему в Java и C ++ определены свои модели памяти. Разработчики компилятора / среды выполнения обязаны обеспечить применение моделей языковой памяти для разных архитектур процессоров.
Тем не менее, если язык не хочет раскрывать низкоуровневое поведение процессора (и готов пожертвовать некоторыми преимуществами производительности современных архитектур), он может выбрать модель памяти, которая полностью скрывает детали «чистого» обращается к памяти, но применяет семантику упорядочения для всех своих операций с памятью. Затем разработчики компилятора / среды выполнения могут выбрать обработку каждой переменной памяти как изменчивой во всех архитектурах процессора. Для этих языков (которые поддерживают совместную память между потоками) нет гонок данных, но они все равно могут быть условиями гонки, даже с языком полной последовательной согласованности.
С другой стороны, модель памяти процессора может быть более строгой (или менее расслабленной, или на более высоком уровне), например, реализовывать последовательную согласованность, как это делал первые процессоры. Затем все операции с памятью упорядочиваются, и никакая гонка данных не существует ни для каких языков, работающих в процессоре.
4. Вывод
Вернемся к исходному вопросу, ИМХО, это нормально определять гонку данных как частный случай состояния гонки, а состояние гонки на одном уровне может стать гонкой данных на более высоком уровне. Это зависит от характера постановки проблемы и от того, где провести границу между неопределенным поведением и четко определенным, но неопределенным поведением. Просто текущее соглашение определяет границу в интерфейсе язык-процессор, не обязательно означает, что это всегда и должно быть так; но текущее соглашение, вероятно, лучше всего отражает современный интерфейс (и мудрость) между архитектором процессора и языком программирования.