Почему возникает взаимная блокировка (deadlock)?

«Почему возникает взаимная блокировка (deadlock)?» — вопрос из категории Операционные системы, который задают на 10% собеседований Python Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Взаимная блокировка (deadlock) возникает, когда два или более потока или процесса бесконечно ожидают ресурсы, которые заняты друг другом, создавая циклическую зависимость. Это приводит к остановке выполнения всех участвующих компонентов.

Для возникновения deadlock должны одновременно выполняться четыре условия Коффмана:

  1. Взаимное исключение (Mutual Exclusion): Ресурсы не могут быть разделены; только один процесс может использовать ресурс в данный момент.
  2. Удержание и ожидание (Hold and Wait): Процесс, уже владеющий одним или несколькими ресурсами, ожидает получения дополнительных ресурсов, занятых другими процессами.
  3. Отсутствие вытеснения (No Preemption): Ресурсы не могут быть принудительно отобраны у процесса, который ими владеет; они могут быть освобождены только добровольно.
  4. Циклическое ожидание (Circular Wait): Существует замкнутая цепочка процессов, где каждый процесс в цепочке ожидает ресурс, занятый следующим процессом в этой цепочке.

Пример на Python (потоки):

import threading
import time

# Два ресурса (блокировки), которые могут быть захвачены
lock1 = threading.Lock()
lock2 = threading.Lock()

def thread_func1():
    print("Поток 1: Попытка захватить lock1...")
    with lock1:
        print("Поток 1: lock1 захвачен. Попытка захватить lock2...")
        time.sleep(0.1) # Имитация работы
        with lock2:
            print("Поток 1: lock2 захвачен. Выполняю работу.")
    print("Поток 1: Все блокировки освобождены.")

def thread_func2():
    print("Поток 2: Попытка захватить lock2...")
    with lock2:
        print("Поток 2: lock2 захвачен. Попытка захватить lock1...")
        time.sleep(0.1) # Имитация работы
        with lock1:
            print("Поток 2: lock1 захвачен. Выполняю работу.")
    print("Поток 2: Все блокировки освобождены.")

t1 = threading.Thread(target=thread_func1)
t2 = threading.Thread(target=thread_func2)

t1.start()
t2.start()

t1.join()
t2.join()
print("Основной поток завершен.")

В этом примере, если t1 захватит lock1 и t2 захватит lock2 почти одновременно, то t1 будет ждать lock2 (который держит t2), а t2 будет ждать lock1 (который держит t1). Это создает циклическое ожидание, и оба потока заблокируются навсегда.

Предотвращение deadlock часто достигается путем нарушения одного или нескольких из этих четырех условий, например, путем упорядочивания захвата ресурсов или использования таймаутов.