Блокируйте оба счёта в одинаковом глобальном порядке и меняйте оба баланса внутри критической секции. Деньги храните целыми минимальными единицами, не 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)
Для нескольких процессов нужны транзакции/блокировки БД и идемпотентный идентификатор перевода. Тест не доказывает отсутствие всех гонок; отдельно проверяют ошибки и встречные переводы. Все изменения балансов должны соблюдать тот же протокол.