Как решать гонку данных
Что проверяют
Понимаете ли вы, что GIL не спасает от гонок, и чем отличается защита в потоках, в asyncio и в базе данных. Следом обычно просят объяснить, почему counter += 1 в двух потоках теряет инкременты.
Как отвечать
Гонка это ситуация, когда результат зависит от порядка выполнения конкурентных участков, которые читают и меняют общее состояние. В CPython GIL защищает целостность интерпретатора, но не ваши операции: counter += 1 раскладывается на чтение, сложение и запись, и поток может переключиться между ними. К тому же в free-threaded сборке CPython, начиная с 3.13, GIL можно отключить совсем, и опираться на него как на блокировку нельзя.
Средства по месту:
threading.LockиRLock: критическая секция в потоках, всегда черезwith lock:, чтобы исключение не оставило блокировку захваченной;queue.Queue: потокобезопасная очередь, лучший способ передать работу между потоками без общих переменных;threading.Semaphore: ограничивает число одновременных участников, например подключений к внешнему API. От гонки на данных защищает только со значением 1, и тогда проще взять обычныйLock;asyncio.Lock: вasyncioкод переключается только наawait, но паттерн «прочитал,await, записал» всё равно гонка;- база данных: в бэкенде большая часть гонок живёт здесь. Атомарный
UPDATE ... SET balance = balance - 100,SELECT ... FOR UPDATE, в Django выраженияF()иselect_for_update(), уникальные ограничения вместо проверки «есть ли уже такая запись».
Хороший ответ заканчивается мыслью, что лучшая синхронизация это отсутствие общего состояния: неизменяемые данные, очередь, отдельные процессы.
Где спотыкаются
Говорят «у Python есть GIL, значит гонок нет». Есть, и тест, который стабильно проходит у вас локально, может падать под нагрузкой.
Считают, что async/await избавляет от гонок. Внутри одного цикла событий нет параллельных потоков, но есть переключения на каждом await, и два обработчика запросов легко перезапишут одно значение.
Ставят threading.Lock внутри корутины. Блокирующий захват останавливает весь цикл событий, для асинхронного кода нужен asyncio.Lock.