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