Что такое race condition?
Состояние гонки (race condition) возникает, когда два или более потоков могут получить доступ к общим данным и пытаются изменить их одновременно. Поскольку алгоритм планирования потоков может переключаться между потоками в любое время, вы не знаете порядок, в котором потоки будут пытаться получить доступ к общим данным. Следовательно, результат изменения данных зависит от алгоритма планирования потоков, т.е. оба потока “соревнуются” за доступ к данным/их изменение.
Проблемы часто возникают, когда один поток выполняет “проверку-затем-действие” (например, “проверяет”, если значение равно 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.
}
Суть в том, что y может быть равно 10, или это может быть что угодно, в зависимости от того, изменил ли другой поток x в промежутке между проверкой и действием. У вас нет реального способа узнать.
Чтобы предотвратить возникновение условий гонки, вы обычно накладываете блокировку на общие данные, чтобы гарантировать, что только один поток может получить доступ к данным одновременно. Это будет означать что-то вроде этого:
// 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
Простой пример из PHP
У нас есть запись логов, и несколько пользователей которые одновременно выполнили действие, по которому производится запись в один лог файл. PHP выполняется в php-fpm и каждый процесс будет пытаться внести запись в файл. Возникнет конкуренция и состояние гонки.
Как решить проблему?
- Одним из наиболее эффективных способов предотвращения уязвимостей состояния гонки является использование механизмов блокировки. Механизмы блокировки обеспечивают одновременный доступ к общему ресурсу только одного процесса, не позволяя другим процессам вмешиваться в работу ресурса.
- Семафоры – это один из видов механизмов блокировки, который часто используется для предотвращения условий гонки. Семафоры работают путем присвоения ресурсу значения, указывающего доступен он или нет. Когда процесс пытается получить доступ к ресурсу, он проверяет значение семафора. Если ресурс недоступен, процесс ждет, пока он не станет доступен.
- Мьютексы – это тип механизма блокировки, аналогичный семафорам. Мьютексы работают, позволяя только одному процессу одновременно получать доступ к общему ресурсу. Когда процесс пытается получить доступ к ресурсу, он проверяет состояние мьютекса. Если к ресурсу уже обращается другой процесс, то он ждет, пока ресурс не станет доступным. Большинство языков программирования имеют встроенную функциональность блокировки данных; например, в Python есть “threading.Lock”, а в Go – “sync.Mutex”.
- Атомарные операции являются разновидностью низкоуровневого механизма синхронизации. Он обеспечивает выполнение операций процесса за один, неделимый шаг. Это не позволяет другим процессам вмешиваться в работу с ресурсом во время выполнения операции.