Перемещение средств между счетами в многопоточной системе

Блокируйте оба счёта в одинаковом глобальном порядке и меняйте оба баланса внутри критической секции. Деньги храните целыми минимальными единицами, не float; проверяйте сумму и достаточность средств.

Короткий ответ

Что ответить на собеседовании

Блокируйте оба счёта в одинаковом глобальном порядке и меняйте оба баланса внутри критической секции. Деньги храните целыми минимальными единицами, не float; проверяйте сумму и достаточность средств.

Подробный разбор

Ответ с пояснениями

Условие

Задача: спроектировать бизнес‑логику, позволяющую переводить денежные средства с одного счёта на другой (метод transfer), а также разработать функциональный тест для этого метода. При реализации необходимо учитывать, что код будет исполняться в условиях многопоточности.

class Account:
    ...

class MoneyTransfer:
    def transfer(source: Account, destination: Account, amount: float):
        ...

class TestMoneyTransfer:
    def test_transfer():
        ...

test = TestMoneyTransfer()
test.test_transfer()

Ответ

Блокируйте оба счёта в одинаковом глобальном порядке и меняйте оба баланса внутри критической секции. Деньги храните целыми минимальными единицами, не float; проверяйте сумму и достаточность средств.

Учебная модель в памяти одного процесса:

from threading import Lock

class Account:
    def __init__(self, balance):
        if type(balance) is not int or balance < 0:
            raise ValueError('Invalid balance')
        self.balance = balance
        self.lock = Lock()

class MoneyTransfer:
    @staticmethod
    def transfer(source, destination, amount):
        if source is destination or type(amount) is not int or amount <= 0:
            raise ValueError('Invalid transfer')
        first, second = sorted((source, destination), key=id)
        with first.lock, second.lock:
            if source.balance < amount:
                raise ValueError('Insufficient funds')
            source.balance -= amount
            destination.balance += amount

Пример проверки конкурентных вызовов:

from concurrent.futures import ThreadPoolExecutor

def test_transfer():
    source, destination = Account(1000), Account(0)
    with ThreadPoolExecutor(max_workers=8) as pool:
        list(pool.map(lambda _: MoneyTransfer.transfer(source, destination, 1),
                      range(100)))
    assert (source.balance, destination.balance) == (900, 100)

Для нескольких процессов нужны транзакции/блокировки БД и идемпотентный идентификатор перевода. Тест не доказывает отсутствие всех гонок; отдельно проверяют ошибки и встречные переводы. Все изменения балансов должны соблюдать тот же протокол.

ИИ-помощник для собеседований

Хочешь уверенно проходить собеседования?

Попробуй ИИ-помощник для собеседований: слышит вас и собеседника, анализирует экран, подсказывает ответы в реальном времени, работает без VPN и не попадает в захват экрана.

Подробнее