Короткий ответ
Deadlock возникает, когда два потока захватывают несколько локов в разном порядке и каждый ждёт лок, уже удержанный другим — надёжное решение — всегда захватывать локи в едином, согласованном порядке во всём коде.
Как это работает подробнее
Поток A захватывает lock1 и ждёт lock2; поток B уже захватил lock2 и ждёт lock1 — оба блокируются навечно, так как ни один не может продолжить без лока, который держит другой.
Пример кода
example-265.py
import threading
def transfer(acc_from, acc_to, amount):
# Безопасный порядок: всегда захватываем локи по id объекта, независимо от порядка аргументов
first, second = sorted([acc_from, acc_to], key=lambda a: id(a))
with first.lock:
with second.lock:
acc_from.balance -= amount
acc_to.balance += amount
# Проверка: запуск transfer(A, B, 10) и transfer(B, A, 5) в двух потоках одновременно
# не зависает, так как оба потока захватывают локи в едином порядкеЧто использовать на практике
- Практика. Определяют глобальный порядок локов (например, по
idобъекта) и всегда захватывают их в этом порядке во всех местах кода; альтернативно используютacquire(timeout=...)и откат/повтор при неудаче, либо избегают удержания нескольких локов одновременно архитектурно.
Подводные камни
- Риски. Захватывают локи в разном порядке в разных функциях, работающих с одними и теми же ресурсами (например, перевод между двумя счетами); не используют
timeoutнаacquire, из-за чего дедлок невозможно детектировать программно — только по зависанию процесса.