Вступить в клуб →
сложнаявопросPython и асинхронность

Проверка баланса и списание в async-функции: откуда взялся отрицательный баланс

В платёжном сервисе списание реализовано так:

async def withdraw(session, user_id, amount):
    balance = await get_balance(session, user_id)
    if balance < amount:
        raise NotEnoughMoney()
    await set_balance(session, user_id, balance - amount)

При балансе 100 пользователь дважды почти одновременно нажал «Оплатить 80». Оба запроса прошли, баланс стал отрицательным.

В чём причина и что исправить?

  • asyncio выполняет корутины строго по очереди, гонок в одном процессе не бывает — ищите баг в get_balance
  • На каждом await управление уходит другой корутине: обе прочитали баланс 100. Нужно атомарное изменение в базе
  • Достаточно завести один asyncio.Lock на приложение и оборачивать в него функцию списания целиком
  • Нужно убрать async: в синхронной функции запросы обработаются последовательно и гонки исчезнут

🔒 Проверка ответа — для участников клуба

  • Проверка ответа
  • Подсказка, если застряли
  • Разбор с объяснением, почему так
  • Прогресс по всем задачам и виртуальные собеседования
Зарегистрироваться →

Регистрация занимает минуту

Другие задачи раздела