Как избежать deadlock в многопоточности
Что проверяют
Понимаете ли вы, откуда берётся взаимная блокировка, и умеете ли убрать саму причину, а не лечить симптом таймаутом. Следом обычно спрашивают, чем deadlock отличается от гонки и бывает ли он в asyncio, где потоков нет вовсе.
Как отвечать
Начните с условий Коффмана: deadlock возможен, только когда одновременно выполнены все четыре. Это взаимное исключение, удержание ресурса с ожиданием следующего, невозможность отнять ресурс и циклическое ожидание. Разорвите любое из них, и блокировки не будет. Дальше практические приёмы:
- единый порядок захвата: все потоки берут замки в одном порядке, например по заранее заданному рангу. Цикл ожидания становится невозможен;
- один замок вместо нескольких: если два ресурса всегда нужны вместе, их защищает один
threading.Lock; - таймаут:
lock.acquire(timeout=1.0)возвращаетFalse, поток отпускает то, что уже держит, и пробует позже. Это страховка, а не решение; withвместо ручногоacquire/release: замок освободится даже при исключении;RLock: нужен, когда функция под замком зовёт другую функцию под тем же замком. ОбычныйLockв этом случае заблокирует поток сам на себе;- очереди вместо общих данных:
queue.Queueи один владелец ресурса убирают разделяемое состояние, а с ним и замки.
Упомяните asyncio: там deadlock тоже случается, если две корутины ждут asyncio.Lock друг друга или событие, которое никто не выставит. Для диагностики зависшего процесса пригодятся faulthandler.dump_traceback_later() и py-spy dump, они показывают стеки всех потоков.
Где спотыкаются
Считают, что GIL защищает от deadlock. GIL лишь не даёт двум потокам одновременно исполнять байткод, а ваши собственные замки блокируют друг друга как угодно.
Предлагают таймауты как главный способ. Таймаут прячет ошибку в порядке захвата, а под нагрузкой потоки начинают по кругу отпускать и снова хватать замки, и получается livelock.
Забывают про вложенные вызовы: метод под замком дёргает колбэк или обработчик логов, который берёт свой замок, и порядок захвата нарушается неявно, в чужом коде.