Что такое состояние гонки? - PullRequest
851 голосов
/ 29 августа 2008

При написании многопоточных приложений одной из наиболее распространенных проблем является состояние гонки.

Мои вопросы к сообществу:

Что такое состояние гонки? Как вы их обнаруживаете? Как вы справляетесь с ними? Наконец, как вы предотвращаете их появление?

Ответы [ 19 ]

1074 голосов
/ 29 августа 2008

Состояние гонки возникает, когда два или более потоков могут получить доступ к общим данным, и они пытаются изменить их одновременно. Поскольку алгоритм планирования потоков может переключаться между потоками в любое время, вы не знаете порядок, в котором потоки будут пытаться получить доступ к общим данным. Следовательно, результат изменения данных зависит от алгоритма планирования потоков, то есть оба потока «стремятся» получить доступ / изменить данные.

Проблемы часто возникают, когда один поток выполняет «проверка-то-действие» (например, «проверка», если значение равно X, тогда «действие», чтобы сделать что-то, что зависит от значения, являющегося X), а другой поток что-то значение между «чеком» и «актом». Например:

if (x == 5) // The "Check"
{
   y = x * 2; // The "Act"

   // If another thread changed x in between "if (x == 5)" and "y = x * 2" above,
   // y will not be equal to 10.
}

Дело в том, что у может быть 10, или это может быть что угодно, в зависимости от того, изменился ли другой поток х между проверкой и действием. У вас нет реального способа узнать.

Чтобы предотвратить возникновение условий гонки, вы обычно устанавливаете блокировку вокруг общих данных, чтобы обеспечить доступ к данным одновременно только одному потоку. Это будет означать что-то вроде этого:

// Obtain lock for x
if (x == 5)
{
   y = x * 2; // Now, nothing can change x until the lock is released. 
              // Therefore y = 10
}
// release lock for x
194 голосов
/ 29 августа 2008

«Состояние гонки» существует, когда многопоточный (или другой параллельный) код, который будет обращаться к общему ресурсу, может сделать это таким образом, чтобы вызвать неожиданные результаты.

Возьмите этот пример:

for ( int i = 0; i < 10000000; i++ )
{
   x = x + 1; 
}

Если у вас было 5 потоков, выполняющих этот код одновременно, значение x НЕ БУДЕТ в итоге 50 000 000. На самом деле это будет меняться с каждым прогоном.

Это потому, что для того, чтобы каждый поток увеличивал значение x, они должны сделать следующее: (очевидно, упрощенно)

Retrieve the value of x
Add 1 to this value
Store this value to x

Любой поток может быть на любом этапе этого процесса в любое время, и они могут наступать друг на друга, когда задействован общий ресурс. Состояние x может быть изменено другим потоком в течение времени между считыванием x и его обратной записью.

Допустим, поток извлекает значение x, но еще не сохранил его. Другой поток может также извлечь такое же значение x (поскольку ни один поток еще не изменил его), и тогда они оба будут хранить то же самое значение (x + 1) обратно в x!

Пример:

Thread 1: reads x, value is 7
Thread 1: add 1 to x, value is now 8
Thread 2: reads x, <b>value is 7</b>
Thread 1: stores 8 in x
Thread 2: adds 1 to x, value is now 8
Thread 2: <b>stores 8 in x</b>

Условия гонки можно избежать, используя какой-то механизм блокировки перед кодом, который обращается к общему ресурсу:

for ( int i = 0; i < 10000000; i++ )
{
   //lock x
   x = x + 1; 
   //unlock x
}

Здесь каждый раз получается 50 000 000 ответов.

Подробнее о блокировке ищите: мьютекс, семафор, критический раздел, общий ресурс.

132 голосов
/ 05 октября 2013

Что такое состояние гонки?

Вы планируете пойти в кино в 5 часов вечера. Вы спрашиваете о наличии билетов в 4 вечера. Представитель говорит, что они есть в наличии. Вы расслабляетесь и добираетесь до кассы за 5 минут до шоу. Я уверен, что вы можете догадаться, что происходит: это фул-хаус. Проблема здесь заключалась в продолжительности между проверкой и действием. Вы спросили в 4 и действовали в 5. В то же время кто-то еще забрал билеты. Это условие гонки - в частности, сценарий «проверяй и действуй» условий гонки.

Как вы их обнаруживаете?

Обзор религиозного кода, многопоточные юнит-тесты. Там нет ярлыка. Существует несколько плагинов Eclipse, но пока нет ничего стабильного.

Как вы справляетесь и предотвращаете их?

Лучше всего было бы создавать функции без побочных эффектов и без сохранения состояния, максимально использовать неизменяемые значения. Но это не всегда возможно. Так что использование java.util.concurrent.atomic, параллельные структуры данных, правильная синхронизация и параллелизм на основе акторов помогут.

Лучший ресурс для параллелизма - JCIP. Вы также можете получить больше подробностей выше объяснения здесь .

60 голосов
/ 29 августа 2013

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

Гонка данных происходит, когда 2 инструкции обращаются к одной и той же ячейке памяти, по крайней мере один из этих обращений является записью, и не происходит до упорядочения среди этих обращений. То, что представляет собой «событие», происходит до того, как упорядочение является предметом множества споров, но в целом пары «блокировка блокировки» для одной и той же переменной блокировки и пары «сигнал ожидания» для одной и той же переменной условия вызывают порядок «происходит до».

Состояние гонки - это семантическая ошибка. Это ошибка, возникающая во времени или порядке событий, которая приводит к ошибочному поведению программы .

Многие состояния гонки могут быть (и на самом деле) вызваны гонками данных, но это не обязательно. Фактически, гонки данных и условия гонки не являются ни необходимым, ни достаточным условием друг для друга. Этот пост в блоге также очень хорошо объясняет разницу, на примере простого банковского перевода. Вот еще один простой пример , который объясняет разницу.

Теперь, когда мы прибегли к терминологии, давайте попробуем ответить на первоначальный вопрос.

Учитывая, что условия гонки - это семантические ошибки, общего способа их обнаружения нет. Это связано с тем, что не существует автоматизированного оракула, который может отличить правильное и неправильное поведение программы в общем случае. Обнаружение расы - неразрешимая проблема.

С другой стороны, гонки данных имеют точное определение, которое не обязательно связано с корректностью, и поэтому их можно обнаружить. Существует множество разновидностей детекторов гонки данных (статическое / динамическое обнаружение гонки данных, обнаружение гонки данных на основе блокировок, обнаружение гонки данных на основе предшествующих событий, обнаружение гибридной гонки данных). Современным динамическим детектором гонки данных является ThreadSanitizer , который очень хорошо работает на практике.

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

33 голосов
/ 29 августа 2008

Каноническое определение типа ", когда два потока обращаются к одному и тому же месту в памяти одновременно, и по крайней мере один из обращений является записью ." В этой ситуации поток «считывателя» может получить старое или новое значение, в зависимости от того, какой поток «выигрывает гонку». Это не всегда ошибка & mdash; на самом деле, некоторые действительно низкоуровневые алгоритмы делают это нарочно & mdash; но обычно этого следует избегать. @ Стив Гери - хороший пример того, когда это может быть проблемой.

31 голосов
/ 29 августа 2008

Состояние гонки - это разновидность ошибки, которая возникает только при определенных временных условиях.

Пример: Представьте, что у вас есть две темы, A и B.

В теме A:

if( object.a != 0 )
    object.avg = total / object.a

В теме B:

object.a = 0

Если поток A вытесняется сразу после проверки, что object.a не равен NULL, B выполнит a = 0, а когда поток A получит процессор, он выполнит «деление на ноль».

Эта ошибка возникает, только когда поток A прерывается сразу после оператора if, это очень редко, но это может произойти.

18 голосов
/ 29 августа 2008

Условия гонки возникают в многопоточных приложениях или многопроцессорных системах. Условием гонки, по своей сути, является все, что предполагает, что две вещи, не относящиеся к одному и тому же потоку или процессу, будут происходить в определенном порядке, не предпринимая шагов для обеспечения того, чтобы они это делали. Обычно это происходит, когда два потока передают сообщения, устанавливая и проверяя переменные-члены класса, к которым оба могут обращаться. Почти всегда существует состояние состязания, когда один поток вызывает sleep, чтобы дать другому потоку время для завершения задачи (если этот сон не находится в цикле с некоторым механизмом проверки).

Инструменты для предотвращения состязаний зависят от языка и операционной системы, но некоторые общие - это мьютексы, критические секции и сигналы. Мьютексы хороши, когда вы хотите убедиться, что вы единственный, кто что-то делает. Сигналы хороши, когда вы хотите убедиться, что кто-то еще закончил что-то делать. Минимизация общих ресурсов также может помочь предотвратить непредвиденное поведение

Определение условий гонки может быть трудным, но есть пара признаков. Код, который в значительной степени зависит от снов, подвержен гонкам, поэтому сначала проверьте наличие вызовов в спящем коде. Добавление особенно длинных снов может также использоваться для отладки, чтобы попытаться вызвать определенный порядок событий. Это может быть полезно для воспроизведения поведения, определения возможности его исчезновения путем изменения сроков и для тестирования решений на месте. Сны должны быть удалены после отладки.

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

16 голосов
/ 04 августа 2017

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

Согласно Википедии :

Термин происходит от идеи двух сигналов, мчащихся друг с другом до сначала повлияет на вывод .

Состояние гонки в логической схеме:

enter image description here

Индустрия программного обеспечения приняла этот термин без изменений, что делает его немного сложным для понимания.

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

  • "два сигнала" => "два потока" / "два процесса"
  • "влиять на вывод" => "влиять на некоторое общее состояние"

Таким образом, состояние гонки в индустрии программного обеспечения означает «два потока» / «два процесса», которые гонятся друг за другом, чтобы «повлиять на некоторое общее состояние», и конечный результат общего состояния будет зависеть от некоторой тонкой разницы во времени, которая может быть определенным порядком запуска потоков / процессов, расписанием потоков / процессов и т. д.

9 голосов
/ 29 августа 2008

Условие гонки - это ситуация при параллельном программировании, когда два параллельных потока или процесса конкурируют за ресурс, и итоговое конечное состояние зависит от того, кто получает ресурс первым.

8 голосов
/ 14 сентября 2012

Microsoft фактически опубликовала действительно подробную статью по этому вопросу о гоночных условиях и тупиках. Наиболее кратким резюме из него будет заголовок абзаца:

Состояние гонки возникает, когда два потока обращаются к общей переменной в в то же время. Первый поток читает переменную, а второй поток читает то же значение из переменной. Тогда первая нить и второй поток выполняет свои операции над значением, и они участвуют в гонке чтобы увидеть, какой поток может записать последнее значение в общую переменную. Значение потока, который записывает свое значение последним, сохраняется, потому что поток записывает значение, которое предыдущий поток написал.

...