Чи насправді «гонки даних» та «стан гонки» - це одне і те саме в контексті одночасного програмування


Відповіді:


141

Ні, це не одне і те ж. Вони не є підмножиною один одного. Вони також не є ні необхідною, ні достатньою умовою один для одного.

Визначення гонки даних досить чітке, і тому його виявлення може бути автоматизовано. Перебіг даних відбувається, коли 2 інструкції з різних потоків отримують доступ до одного і того ж місця в пам'яті, принаймні один із цих звернень є записом, і немає синхронізації, яка вимагає будь-якого конкретного порядку серед цих звернень.

Умова раси - це семантична помилка. Це недолік, який виникає у термінах або впорядкуванні подій, що призводить до помилкової поведінки програми. Багато умов перегонів можуть бути спричинені перегонами даних, але це не є необхідним.

Розглянемо наступний простий приклад, де x - спільна змінна:

Thread 1    Thread 2

 lock(l)     lock(l)
 x=1         x=2
 unlock(l)   unlock(l)

У цьому прикладі записи в х із потоків 1 і 2 захищені блокуваннями, тому вони завжди відбуваються в якомусь порядку, забезпеченому порядком, з яким блокування отримуються під час виконання. Тобто, атомарність запису не може бути порушена; завжди трапляється так, що стосунки між цими двома записами виконуються. Ми просто не можемо знати, який запис відбувається раніше, ніж інші апріорі.

Немає фіксованого впорядкування між записами, оскільки блокування не можуть цього забезпечити. Якщо правильність програм порушена, скажімо, коли після запису в x потоком 2 слідує запис у x у потоці 1, ми говоримо, що існує умова раси, хоча технічно немає гонки даних.

Значно корисніше виявляти умови перегонів, ніж перегони даних; однак цього також дуже важко досягти.

Побудова зворотного прикладу також є тривіальною. Цей допис у блозі також дуже добре пояснює різницю на простому прикладі банківських операцій.


1
"гонка даних (...) відсутність синхронізації, яка вимагає певного порядку серед цих звернень." Я трохи розгублений. У вашому прикладі операції можуть виконуватися в обох замовленнях (або = 1, тоді = 2, або навпаки). Чому це не гонка даних?
josinalvo

6
@josinalvo: це артефакт технічного визначення гонки даних. Ключовим моментом є те, що між двома зверненнями відбуватиметься розблокування та отримання блокування (для будь-якого з можливих замовлень). За визначенням, звільнення блокування та отримання блокування встановлює порядок між двома зверненнями, і тому не відбувається гонки даних.
Baris Kasikci

Синхронізація ніколи не вимагає певного порядку між операційними системами, тому це не дуже вдалий спосіб виразити це. З іншого боку, JMM вказує, що для кожної операції зчитування повинна існувати певна операція запису, яку вона спостерігає, навіть під час гонки даних. Важко уникнути явного згадування подій раніше і порядку синхронізації, проте навіть визначення JLS є помилковим, коли згадується просто раніше - раніше : за його визначенням, два одночасних волатильних записи складають гонку даних.
Marko Topolnik

@BarisKasikci "встановлює порядок" не має жодного реального значення, що стосується мене. Це просто ласкаві слова. Я чесно не вірю, що "гонка даних" є надзвичайно корисним поняттям, оскільки буквально кожне місце пам'яті, до якого отримують доступ декілька потоків, можна вважати таким, що перебуває в "гонці даних"
Нолдорін,

Випуск-придбання пар завжди встановлює порядок. Загальне пояснення є довгим, але тривіальним прикладом є пара сигнал-очікування. @Noldorin "Встановлює порядок" відноситься до порядку, що відбувся раніше, що є ключовим поняттям теорії паралельності (див. Основний документ Лампорта про взаємозв'язок, що відбувся до) і розподілених систем. Раси даних є корисною концепцією, оскільки їх наявність створює багато питань (наприклад, невизначена семантика відповідно до моделі пам'яті C ++, дуже складна семантика в Java тощо). Їх виявлення та усунення становлять величезну літературу в наукових дослідженнях і на практиці.
Баріс Касіччі

20

Згідно з Вікіпедією, термін "умова перегони" використовується з часів появи перших електронно-логічних воріт. У контексті Java умова раси може стосуватися будь-якого ресурсу, наприклад, файлу, мережевого з'єднання, потоку з пулу потоків тощо.

Термін "гонка даних" найкраще зарезервувати для його конкретного значення, визначеного JLS .

Найцікавіший випадок - це стан перегонів, який дуже схожий на перегони даних, але все ще не такий, як у цьому простому прикладі:

class Race {
  static volatile int i;
  static int uniqueInt() { return i++; }
}

Оскільки iнестабільна, то немає даних про швидкість передачі даних; однак, з точки зору коректності програми, існує стан перегонів через неатомність двох операцій: читання i, запис i+1. Кілька потоків можуть отримувати одне і те ж значення від uniqueInt.


1
Ви можете опустити рядок у своїй відповіді, що описує, що data raceнасправді означає JLS?
Geek

@geek Слово "JLS" - це гіперпосилання на відповідний розділ JLS.
Marko Topolnik

@MarkoTopolnik Мене трохи бентежить приклад. Не могли б ви пояснити: "Оскільки я нестабільний, то немає даних"? Нестабільність лише забезпечувала його видимість, але все-таки: 1) вона не синхронізована і декілька потоків можуть читати / писати одночасно і 2) Це спільне нефінальне поле Отже, згідно з Java Concurrency in Practice (також наведено нижче) , це раса даних, а не стан раси, чи не так?
aniliitb10

@ aniliitb10 Замість того, щоб покладатися на цитати, вирвані з їхнього контексту, вам слід переглянути розділ 17.4 JLS, на який я посилався у своїй відповіді. Доступ до летючої змінної є дією синхронізації, як визначено у §17.4.2.
Марко Топольник

@ aniliitb10 Votaltiles не спричиняють швидкості передачі даних, оскільки їх доступ можна замовити. Тобто ви можете міркувати про їх порядок так чи інакше, приводячи до іншого результату. За допомогою обміну даними у вас немає можливості обґрунтувати замовлення. Наприклад, операція i ++ кожного потоку може просто відбуватися з відповідним локально кешованим значенням i. У цілому у вас немає можливості замовити ці операції (з точки зору програміста) - якщо у вас не встановлена ​​певна модель пам'яті.
Сяо-Фен Лі

3

Ні, вони різні, і жоден з них не є підмножиною одного чи навпаки.

Термін "гонка" часто плутають із пов'язаним терміном "гонка даних", що виникає, коли синхронізація не використовується для координації всього доступу до спільного нефінального поля. Ви ризикуєте гонкою даних кожного разу, коли потік пише змінну, яка може бути прочитана іншим потоком, або читає змінну, яка могла бути останньою записаною іншим потоком, якщо обидва потоки не використовують синхронізацію; код із расами даних не має корисної визначеної семантики в рамках моделі пам'яті Java. Не всі умови перегонів є перегонами даних, і не всі перегони даних є перегонами, але обидва вони можуть спричинити збій одночасних програм непередбачуваними способами.

Взяті з чудової книги - " Яка паралельність на практиці" Джошуа Блоха і Ко.


Зауважте, що питання має мовно-агностичний тег.
martinkunev

1

TL; DR: Різниця між расою даних та умовою раси залежить від природи постановки проблеми та від того, де провести межу між невизначеною поведінкою та чітко визначеною, але невизначеною поведінкою. Поточна різниця є загальноприйнятою і найкраще відображає інтерфейс між архітектором процесора та мовою програмування.

1. Семантика

Раса даних конкретно відноситься до несинхронізованих конфліктуючих "доступу до пам'яті" (або дій, або операцій) до того самого місця пам'яті. Якщо конфлікт у доступі до пам'яті відсутній, і все ще існує невизначена поведінка, спричинена впорядкуванням операцій, це умова перегони.

Примітка "доступ до пам'яті" тут має конкретне значення. Вони відносяться до "чистого" завантаження пам'яті або зберігання дій, без додаткової семантики. Наприклад, сховище пам'яті з одного потоку (не обов'язково) знає, скільки часу потрібно для запису даних у пам'ять, і нарешті розповсюджується на інший потік. Для іншого прикладу, сховище пам'яті в одному місці перед іншим сховищем в інше місце за тим самим потоком не (обов'язково) гарантує, що перші дані, записані в пам'яті, випереджають друге. Як наслідок, порядок цих чистого доступу до пам'яті (не обов'язково) може бути "аргументованим" , і може статися що завгодно, якщо не визначено інше.

Коли "доступ до пам'яті" чітко визначений з точки зору упорядкування за допомогою синхронізації, додаткова семантика може гарантувати, що, навіть якщо терміни доступу до пам'яті невизначені, їх порядок може бути "аргументованим" за допомогою синхронізації. Зверніть увагу, що хоча порядок доступу між пам’яттю може бути обґрунтованим, вони не обов’язково визначаються, отже, і стан перегонів.

2. Чому різниця?

Але якщо порядок все ще невизначений у стані перегонів, навіщо намагатись відрізняти його від даних перегонів? Причина в практичному, а не в теоретичному. Це тому, що в інтерфейсі існує різниця між мовою програмування та архітектурою процесора.

Інструкція про завантаження / зберігання пам'яті в сучасній архітектурі зазвичай реалізується як "чистий" доступ до пам'яті через природу невпорядкованого конвеєра, спекуляції, багаторівневого кешу, взаємозв'язку процесора, особливо багатоядерного тощо. Існує безліч факторів, що призводять до невизначеності термінів та впорядкування. Застосування порядку для кожної інструкції з пам'яті несе величезні штрафи, особливо в процесорі, що підтримує багатоядерність. Тож семантика впорядкування надається з додатковими інструкціями, такими як різні бар’єри (або огорожі).

Гонка даних - це ситуація виконання команд процесора без додаткових заборів, які допомагають обгрунтувати порядок доступу конфліктуючих пам’яті. Результат є не тільки невизначеним, але й, можливо, дуже дивним, наприклад, два записи в одне і те ж розташування слова різними потоками можуть призвести до кожної написаної половини слова або можуть працювати лише на їх локально кешованих значеннях. - Це невизначена поведінка, з точки зору програміста. Але вони (як правило) добре визначені з точки зору архітектора процесора.

Програмісти повинні мати спосіб міркувати виконання свого коду. Змагання з даними - це те, що вони не можуть мати сенсу, тому їх слід завжди уникати (зазвичай). Ось чому специфікації мови, які є достатньо низькими, зазвичай визначають гонку даних як невизначену поведінку, відмінну від чітко визначеної поведінки пам’яті стану гонки.

3. Моделі пам'яті мови

Різні процесори можуть мати різну поведінку доступу до пам'яті, тобто модель пам'яті процесора. Програмістам незручно вивчати модель пам’яті кожного сучасного процесора, а потім розробляти програми, які можуть отримати від них користь. Бажано, якщо мова може визначити модель пам’яті, щоб програми цієї мови завжди поводились так, як очікувалося, як визначає модель пам’яті. Ось чому Java та C ++ мають визначені моделі пам'яті. Розробники компілятора / середовища виконання несуть обов'язок забезпечити застосування моделей мовної пам'яті в різних архітектурах процесорів.

Тим не менш, якщо мова не хоче викривати низький рівень поведінки процесора (і готовий пожертвувати певними перевагами продуктивності сучасних архітектур), вони можуть вибрати, щоб визначити модель пам'яті, яка повністю приховує деталі "чистого" доступу до пам'яті, але застосовуйте семантику впорядкування для всіх їх операцій з пам'яттю. Тоді розробники компілятора / середовища виконання можуть обрати трактування кожної змінної пам'яті як мінливості у всіх процесорних архітектурах. Для цих мов (які підтримують спільну пам'ять між потоками) немає раси даних, але все одно можуть бути расовими умовами, навіть з мовою повної послідовної послідовності.

З іншого боку, модель пам'яті процесора може бути жорсткішою (або менш спокійною, або на більш високому рівні), наприклад, реалізуючи послідовну послідовність, як це робив процесор на ранніх днях. Тоді всі операції з пам'яттю упорядковуються, і не існує гонки даних для жодної мови, що працює в процесорі.

4. Висновок

Повертаючись до початкового питання, IMHO чудово визначити расу даних як особливий випадок стану гонки, і стан гонки на одному рівні може стати гонкою даних на більш високому рівні. Це залежить від природи постановки проблеми та від того, де провести межу між невизначеною поведінкою та чітко визначеною, але невизначеною поведінкою. Просто поточна конвенція визначає межі інтерфейсу мова-процесор, не обов'язково означає, що це завжди і повинно бути так; але нинішня конвенція, мабуть, найкраще відображає найсучасніший інтерфейс (і мудрість) між архітектором процесора та мовою програмування.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.