> Что такое дедлоки? (Python)

Уровень: junior · Роль: backend · Язык: Python · Категория: Технические вопросы

Компании: HeadHunter

Стек: Python

> Пример ответа

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

Дедлок - это ситуация в многопоточном или многопроцессном приложении, когда два или более потока блокируют друг друга, ожидая ресурсы, которые удерживают другие потоки, и ни один не может продолжить выполнение. В результате программа зависает навсегда. В Python дедлоки чаще всего возникают при использовании threading.Lock или asyncio.Lock, когда порядок захвата блокировок не согласован.

Подробное объяснение

Дедлок возникает при выполнении четырёх условий Коффмана:

  1. Взаимное исключение - ресурс может быть занят только одним потоком одновременно.
  2. Удержание и ожидание - поток удерживает один ресурс и ждёт другой.
  3. Невытесняемость - ресурс нельзя принудительно забрать у потока.
  4. Циклическое ожидание - существует цикл потоков, где каждый ждёт ресурс, удерживаемый следующим.

Классический пример - два потока, каждый захватывает свою блокировку, а затем пытается захватить блокировку другого. Если оба сделают это одновременно, они будут ждать вечно.

В Python дедлоки особенно опасны, потому что GIL не защищает от них - GIL управляет только выполнением байткода, а не логикой блокировок. Дедлок может произойти и в asyncio, если корутина ждёт событие, которое никогда не наступит.

На практике

На практике дедлоки проявляются как зависание программы без ошибок и без потребления CPU. Их сложно отлаживать, потому что они не всегда воспроизводятся - зависят от тайминга потоков.

Основные способы борьбы:

  • Фиксированный порядок захвата блокировок - всегда захватывать блокировки в одном и том же порядке.
  • Таймауты - использовать lock.acquire(timeout=...), чтобы не ждать вечно.
  • Использование contextlib - захватывать несколько блокировок через with в правильном порядке.
  • Избегать вложенных блокировок - если возможно, перепроектировать код.
  • Использовать threading.RLock - если нужна повторная блокировка тем же потоком.

В Python также есть threading.Lock и threading.RLock, а для asyncio - asyncio.Lock. Важно помнить, что asyncio.Lock не потокобезопасен, его нельзя использовать из разных потоков.

Пример кода

PYTHON
import threading
import time
lock_a = threading.Lock()
lock_b = threading.Lock()
def worker_1():
with lock_a:
print("worker_1: захватил lock_a")
time.sleep(0.1)
with lock_b:
print("worker_1: захватил lock_b")
def worker_2():
with lock_b:
print("worker_2: захватил lock_b")
time.sleep(0.1)
with lock_a:
print("worker_2: захватил lock_a")
t1 = threading.Thread(target=worker_1)
t2 = threading.Thread(target=worker_2)
t1.start()
t2.start()
t1.join()
t2.join()

Здесь оба потока захватывают блокировки в разном порядке, что приводит к дедлоку. Исправление - захватывать блокировки в одном порядке в обоих потоках.

Как отвечать на собеседовании

Начни с определения дедлока и перечисли четыре условия Коффмана. Затем приведи простой пример с двумя блокировками. Объясни, почему это проблема именно для Python - GIL не спасает. Упомяни способы предотвращения: фиксированный порядок, таймауты, минимизация вложенных блокировок. Если спросят про asyncio - скажи, что там тоже возможны дедлоки, но механизм другой.

Что проверяет интервьюер

Интервьюер проверяет:

  • понимание базовых концепций многопоточности;
  • знание условий возникновения дедлока;
  • умение анализировать код на предмет потенциальных дедлоков;
  • знание практических методов предотвращения;
  • понимание особенностей Python (GIL, asyncio).

Типичные ошибки

  • Путать дедлок с гонкой данных или livelock.
  • Думать, что GIL предотвращает дедлоки.
  • Не упоминать условия Коффмана.
  • Предлагать только один способ решения, например, только таймауты.
  • Не учитывать, что дедлок может быть не только с threading.Lock, но и с другими примитивами синхронизации.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью