Какие проблемы возникают при одновременном доступе нескольких потоков к общей переменной

Ответ

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

Чаще всего это происходит из-за неатомарных операций. Например, операция инкремента (counter += 1) на самом деле состоит из трех шагов:

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

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

Пример на Python, демонстрирующий race condition:

import threading

# Общая глобальная переменная
counter = 0

def increment():
    global counter
    for _ in range(100000):
        # Критическая секция: неатомарная операция
        counter += 1

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

for t in threads:
    t.start()

for t in threads:
    t.join()

# Ожидаемый результат: 1_000_000
# Реальный результат будет меньше из-за потерянных инкрементов
print(f"Финальное значение счетчика: {counter}")

Другие связанные проблемы:

  • Deadlock (взаимная блокировка): Два или более потока вечно ожидают друг друга, чтобы освободить ресурсы, которые они заблокировали.
  • Неконсистентность данных: Чтение данных в промежуточном, невалидном состоянии, пока другой поток их изменяет.

Решение:

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

Исправленный пример с threading.Lock:

import threading

counter = 0
lock = threading.Lock()

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

# ... (создание и запуск потоков с target=safe_increment)
# ...
# print(counter) -> Выведет корректный результат 1_000_000