Что такое Deadlock и каковы условия его возникновения?

Ответ

Deadlock (взаимная блокировка) — это ситуация в многопоточной среде, когда два или более потока находятся в состоянии бесконечного ожидания ресурсов, захваченных друг другом, и ни один из них не может продолжить выполнение.

Для возникновения Deadlock необходимо одновременное соблюдение четырех условий Коффмана:

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

Классический пример на Python:

import threading
import time

lock_a = threading.Lock()
lock_b = threading.Lock()

def process_one():
    print("Процесс 1: Пытаюсь захватить lock_a...")
    with lock_a:
        print("Процесс 1: Захватил lock_a.")
        time.sleep(0.1) # Даем время другому потоку захватить lock_b
        print("Процесс 1: Пытаюсь захватить lock_b...")
        with lock_b: # Зависнет здесь, если lock_b захвачен Процессом 2
            print("Процесс 1: Захватил lock_b.")

def process_two():
    print("Процесс 2: Пытаюсь захватить lock_b...")
    with lock_b:
        print("Процесс 2: Захватил lock_b.")
        print("Процесс 2: Пытаюсь захватить lock_a...")
        with lock_a: # Зависнет здесь, если lock_a захвачен Процессом 1
            print("Процесс 2: Захватил lock_a.")

t1 = threading.Thread(target=process_one)
t2 = threading.Thread(target=process_two)
t1.start()
t2.start()
t1.join()
t2.join()

print("Завершено.") # Этот код никогда не выполнится

Способы предотвращения:

  • Соблюдение порядка блокировок: Все потоки должны запрашивать блокировки в одном и том же порядке (например, всегда сначала lock_a, потом lock_b).
  • Использование тайм-аутов: Захватывать блокировку с тайм-аутом (lock.acquire(timeout=1)), чтобы избежать вечного ожидания.

Ответ 18+ 🔞

А, ну это классика, ёпта! Deadlock, блядь! Взаимная блокировка, сука! Это когда твои потоки, такие умные, настолько друг друга заебут, что все встают колом, как будто им всем по пизде дали одновременно.

Представь, два потока, два мудака, сидят и смотрят друг на друга в пустые глазницы. Один говорит: «Дай ресурс А, я тебе тогда отдам Б». А второй: «Ахуел? Сначала ты мне Б отдай, тогда я тебе А кину». И стоят так до скончания времён, ебаные упёртые ослы. Ни один не может двинуться, потому что ждёт, пока другой сдаст позиции. Пиздец, а не работа.

Для этого цирка нужно аж четыре условия, как четыре ноги у стула. Сломай одну — и вся конструкция набок, deadlock не случится.

  1. Взаимное исключение. Ресурс — штука единоличная, как последняя бутылка пива в холодильнике. Один выпил — второму не достанется, пока первый не отстанет.
  2. Удержание и ожидание. Это когда чувак уже бутылку в руке держит, но орёт: «Не отдам, пока мне чипсов не поднесешь!». А чипсы у другого.
  3. Отсутствие вытеснения. Нельзя просто так взять и вырвать бутылку из его цепких лап. Он должен сам её отпустить, но он не отпустит, пока чипсов не получит. Замкнутый круг, блядь.
  4. Циклическое ожидание. И вот тут начинается пиздец. Первый держит бутылку и хочет чипсы у второго. Второй держит чипсы и хочет бутылку у первого. И оба нихуя не делают, только сопят от злости. Цикл, сука!

Вот тебе наглядная пьеса на Python, как эти два дебила друг друга в петлю загоняют:

import threading
import time

lock_a = threading.Lock()
lock_b = threading.Lock()

def process_one():
    print("Процесс 1: Пытаюсь захватить lock_a...")
    with lock_a:
        print("Процесс 1: Захватил lock_a.")
        time.sleep(0.1) # Даем время другому потоку захватить lock_b
        print("Процесс 1: Пытаюсь захватить lock_b...")
        with lock_b: # Зависнет здесь, если lock_b захвачен Процессом 2
            print("Процесс 1: Захватил lock_b.")

def process_two():
    print("Процесс 2: Пытаюсь захватить lock_b...")
    with lock_b:
        print("Процесс 2: Захватил lock_b.")
        print("Процесс 2: Пытаюсь захватить lock_a...")
        with lock_a: # Зависнет здесь, если lock_a захвачен Процессом 1
            print("Процесс 2: Захватил lock_a.")

t1 = threading.Thread(target=process_one)
t2 = threading.Thread(target=process_two)
t1.start()
t2.start()
t1.join()
t2.join()

print("Завершено.") # Этот код никогда не выполнится

Видишь? Они оба захватят по первому замку, а потом оба упрутся лбами, пытаясь взять второй. И всё, приехали. Программа повисла навеки, или пока ты её не прибьёшь. «Завершено» ты в жизни не увидишь, как свои уши.

Как не попасть в эту ебучую ловушку?

  • Порядок, блядь, порядок! Самый надёжный способ. Заставь всех своих потоков-распиздяев захватывать замки в одной и той же последовательности. Всегда сначала lock_a, потом уже lock_b. Тогда один мудак захватит А и Б, сделает свои дела, отпустит, и только потом второй начнёт. Никакого цикла не будет.
  • Тайм-ауты, ёпта! Не жди вечно, как лох. Пытайся захватить замок с таймаутом: lock.acquire(timeout=1). Не получилось за секунду — отпусти всё, что держишь, отойди, подумай о жизни, попробуй снова. Может, пронесёт.

Вот и вся наука. Главное — не создавай условий для этих ёбаных циклических ожиданий, а то потом будешь как Герасим, который и Муму утопил, и сам потом с тоски сдох.