> Как работает дедлок и как его избежать (iOS, Swift)

Уровень: senior · Роль: mobile · Категория: Технические вопросы

Компании: Axel Pro

Стек: iOS, Swift

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

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

Дедлок - это состояние, когда два или более потока блокируют друг друга, ожидая ресурсы, удерживаемые другими потоками, и ни один не может продолжить выполнение. В iOS-разработке чаще всего встречается при работе с NSLock, DispatchQueue.sync или os_unfair_lock. Основные способы избежать: не вкладывать блокировки, использовать единый порядок захвата, избегать sync на той же очереди, применять DispatchQueue.async или os_unfair_lock вместо NSLock там, где это возможно.

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

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

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

В iOS-контексте классический пример - вызов DispatchQueue.main.sync из главной очереди или вложенные блокировки:

SWIFT
let lockA = NSLock()
let lockB = NSLock()
// Поток 1
lockA.lock()
lockB.lock() // ждёт lockB
// Поток 2
lockB.lock()
lockA.lock() // ждёт lockA

Оба потока блокируются навсегда. Также дедлок возможен при использовании DispatchQueue.sync на последовательной очереди, если вызвать её изнутри той же очереди.

На практике

В реальной iOS-разработке дедлоки чаще всего возникают:

  • при синхронном вызове на главной очереди из главного потока;
  • при использовании нескольких NSLock в разном порядке;
  • при работе с DispatchSemaphore внутри той же очереди;
  • при использовании OperationQueue с зависимостями, образующими цикл.

Практические рекомендации:

  • Не вызывайте sync на той же очереди - используйте async или проверяйте Thread.isMainThread.
  • Фиксируйте порядок захвата блокировок - если всегда захватывать lockA перед lockB, цикл невозможен.
  • Используйте os_unfair_lock или NSLock с минимальной критической секцией - чем меньше код под блокировкой, тем меньше шанс пересечения.
  • Применяйте DispatchQueue с барьерами (queue.async(flags: .barrier)) для read-write паттернов вместо ручных блокировок.
  • Избегайте вложенных блокировок - если возможно, реструктурируйте код.
  • Используйте таймауты для DispatchSemaphore (wait(timeout:)), чтобы не блокироваться вечно.

Пример кода

Дедлок на главной очереди:

SWIFT
// Вызов из главного потока
DispatchQueue.main.sync {
// никогда не выполнится - дедлок
}

Исправление:

SWIFT
if Thread.isMainThread {
// выполняем код напрямую
updateUI()
} else {
DispatchQueue.main.async {
updateUI()
}
}

Дедлок с двумя блокировками:

SWIFT
let lockA = NSLock()
let lockB = NSLock()
// Поток 1
lockA.lock()
lockB.lock()
// ...
lockB.unlock()
lockA.unlock()
// Поток 2 - тот же порядок захвата
lockA.lock()
lockB.lock()
// ...
lockB.unlock()
lockA.unlock()

Безопасный вариант с единым порядком - дедлок невозможен, даже если потоки выполняются параллельно.

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

Начните с краткого определения и четырёх условий Коффмана. Затем приведите конкретный пример из iOS - DispatchQueue.main.sync или вложенные NSLock. Покажите, как диагностировать дедлок: использование lldb с bt (backtrace) для всех потоков, DispatchQueue.main в стеке, застывшие потоки. Затем перечислите стратегии предотвращения: единый порядок захвата, избегание sync, таймауты, барьеры. Завершите примером кода с исправлением.

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

  • понимание модели памяти и многопоточности в iOS;
  • знание GCD и NSLock/os_unfair_lock;
  • умение анализировать условия возникновения дедлока;
  • способность предложить практическое решение, а не только теорию;
  • знание инструментов диагностики (Xcode, Instruments, lldb).

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

  • ответ только про DispatchQueue.main.sync, без упоминания блокировок;
  • незнание условий Коффмана;
  • предложение использовать async всегда, без учёта гонок данных;
  • игнорирование таймаутов и os_unfair_lock;
  • путаница между дедлоком, гонкой данных и priority inversion;
  • отсутствие примера кода или некорректный пример с sync на глобальной очереди (это не дедлок).

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

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