Что такое взаимная блокировка (Deadlock) в многопоточном программировании?

«Что такое взаимная блокировка (Deadlock) в многопоточном программировании?» — вопрос из категории Многопоточность, который задают на 31% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

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

Условия возникновения (Условия Коффмана):

  1. Взаимное исключение: Ресурс не может быть использован несколькими потоками одновременно.
  2. Удержание и ожидание: Поток удерживает один ресурс и ждет другой.
  3. Отсутствие вытеснения: Ресурс нельзя отнять у потока.
  4. Циклическое ожидание: Возникает круговая зависимость потоков по ресурсам.

Классический пример на Swift (с NSLock):

let lockA = NSLock()
let lockB = NSLock()

DispatchQueue.global().async {
    lockA.lock()          // Поток 1 захватывает lockA
    sleep(1)              // Имитация работы
    lockB.lock()          // Поток 1 ЖДЁТ lockB (но его держит Поток 2)
    // Критическая секция...
    lockB.unlock()
    lockA.unlock()
}

DispatchQueue.global().async {
    lockB.lock()          // Поток 2 захватывает lockB
    sleep(1)              // Имитация работы
    lockA.lock()          // Поток 2 ЖДЁТ lockA (но его держит Поток 1)
    // Критическая секция...
    lockA.unlock()
    lockB.unlock()
}
// Оба потока заблокированы навсегда.

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

  • Упорядочивание блокировок: Всегда захватывать ресурсы в одинаковом глобальном порядке (сначала A, потом B).
  • Использование NSLock.try() или блокировок с таймаутом.
  • Отказ от явных блокировок в пользу высокоуровневых механизмов GCD (очереди, DispatchSemaphore, DispatchGroup).
  • Проектирование без состояний или с использованием потокобезопасных структур данных.