← Назад к вопросам

Где встречается Race Condition?

2.0 Middle🔥 141 комментариев
#Асинхронность и многопоточность#Безопасность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Понятие Race Condition

Race Condition (состояние гонки) — это критическая ошибка конкурентности, которая возникает, когда результат программы зависит от порядка выполнения потоков или процессов. Несколько потоков одновременно обращаются к общему ресурсу и изменяют его, вызывая непредсказуемые результаты.

Где встречается Race Condition

1. Многопоточное программирование (Multithreading)

Самое распространённое место возникновения — при работе с несколькими потоками, обращающимися к общим данным:

import threading

counter = 0

def increment():
    global counter
    for _ in range(1000000):
        counter += 1  # Race condition here!

threads = [threading.Thread(target=increment) for _ in range(2)]
for t in threads:
    t.start()
for t in threads:
    t.join()

print(counter)  # Может быть <2000000 вместо 2000000

Проблема: операция counter += 1 — это три операции:

  1. Прочитать значение counter
  2. Увеличить на 1
  3. Записать обратно

Между шагами 1 и 3 другой поток может вмешаться и изменить counter.

2. Доступ к файловой системе

Много процессов пытаются одновременно читать и писать в один файл:

import os
import time

# Процесс 1
with open(data.txt, r) as f:
    value = int(f.read())
value += 1
time.sleep(0.1)  # Здесь другой процесс может изменить файл!
with open(data.txt, w) as f:
    f.write(str(value))

# Процесс 2 может сделать то же самое параллельно

3. Работа с базами данных

Одна из самых частых проблем в веб-приложениях. Два запроса читают одно и то же значение и затем пытаются обновить его:

-- Поток 1
SELECT balance FROM accounts WHERE id = 1;  -- balance = 100
-- Параллельно:
-- Поток 2
SELECT balance FROM accounts WHERE id = 1;  -- balance = 100

-- Затем оба вычисляют и пишут:
-- Поток 1
UPDATE accounts SET balance = 150 WHERE id = 1;  -- 100 + 50
-- Поток 2
UPDATE accounts SET balance = 80 WHERE id = 1;   -- 100 - 20

-- Финальный результат: 80 вместо 130!

4. Кэширование в распределённых системах

# Два веб-сервера одновременно проверяют кэш
if not cache.get("user_1"):
    # Оба попали сюда в один момент!
    user_data = db.fetch_expensive_query()
    cache.set("user_1", user_data)

5. Синглтон паттерн (Double-Checked Locking)

class Singleton:
    _instance = None
    
    def __new__(cls):
        if cls._instance is None:  # Race condition!
            cls._instance = super().__new__(cls)
        return cls._instance

# Два потока могут одновременно пройти проверку if и создать две экземпляра

Как избежать Race Condition

1. Использование Mutex/Lock

import threading

counter = 0
lock = threading.Lock()

def increment():
    global counter
    for _ in range(1000000):
        with lock:
            counter += 1

threads = [threading.Thread(target=increment) for _ in range(2)]
for t in threads:
    t.start()
for t in threads:
    t.join()

print(counter)  # 2000000 — всегда правильно

2. Использование SELECT FOR UPDATE в БД

BEGIN;
SELECT balance FROM accounts WHERE id = 1 FOR UPDATE;
-- Теперь другие транзакции будут ждать
UPDATE accounts SET balance = balance + 50 WHERE id = 1;
COMMIT;

3. Использование RLock при вложенных блокировках

import threading

lock = threading.RLock()  # Позволяет одному потоку заблокировать несколько раз

def function1():
    with lock:
        function2()

def function2():
    with lock:  # RLock позволит, обычный Lock вызовет deadlock
        pass

4. Использование Queue для передачи данных

import threading
import queue

q = queue.Queue()

def producer():
    for i in range(10):
        q.put(i)  # Потокобезопасная операция

def consumer():
    while True:
        item = q.get()  # Потокобезопасная операция
        print(item)

5. Использование asyncio вместо threading

import asyncio

counter = 0

async def increment():
    global counter
    for _ in range(1000000):
        counter += 1  # No race condition — код выполняется в одном потоке!

async def main():
    await asyncio.gather(increment(), increment())

asyncio.run(main())
print(counter)  # 2000000

Выводы

Race Condition — это опасный класс ошибок, который сложно отлаживать, так как проявляется непредсказуемо. Главные места её возникновения: многопоточность, доступ к общим ресурсам (файлы, БД, кэш, память). Решения: правильная синхронизация (локи, семафоры), атомарные операции и выбор правильной парадигмы конкурентности (asyncio вместо threading).