Что такое состояние гонки (race condition) в многопоточном программировании

Ответ

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

Ключевая проблема: неатомарность операций

Даже простая операция, как counter += 1, на самом деле состоит из нескольких шагов:

  1. Чтение текущего значения counter из памяти.
  2. Увеличение этого значения в регистре процессора.
  3. Запись нового значения обратно в память.

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

Пример на Python

Этот код должен в итоге вывести 1_000_000, но из-за состояния гонки результат будет непредсказуемо меньше.

import threading

counter = 0

def increment():
    global counter
    for _ in range(100000):
        # Эта операция неатомарна и вызывает race condition
        counter += 1

threads = [threading.Thread(target=increment) for _ in range(10)]

for t in threads:
    t.start()

for t in threads:
    t.join()

print(f"Финальное значение счетчика: {counter}")
# Пример вывода: Финальное значение счетчика: 345123

Решение: использование блокировок (Locks)

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

import threading

counter = 0
lock = threading.Lock()

def safe_increment():
    global counter
    for _ in range(100000):
        with lock: # Блокировка приобретается при входе в блок
            # Этот код выполняет только один поток одновременно
            counter += 1
        # Блокировка автоматически освобождается при выходе

threads = [threading.Thread(target=safe_increment) for _ in range(10)]

for t in threads:
    t.start()

for t in threads:
    t.join()

print(f"Финальное значение счетчика: {counter}")
# Вывод всегда будет: Финальное значение счетчика: 1000000