> Сколько минимум потоков нужно для дедлока? (iOS, Swift)

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

Компании: Сбер

Стек: iOS, Swift

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

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

Минимум два потока. Дедлок - это ситуация, когда каждый поток удерживает ресурс и ждёт ресурс, удерживаемый другим потоком. Один поток не может создать классический deadlock, потому что ему не с кем конкурировать за ресурсы. Однако на практике в iOS дедлок может возникнуть и с одним потоком, если использовать DispatchQueue.main.sync из самой main queue - это частный случай, который называют "deadlock на одной очереди".

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

Классический deadlock требует как минимум двух потоков и двух ресурсов. Условия Коффмана: mutual exclusion, hold and wait, no preemption, circular wait. При двух потоках circular wait выполняется тривиально: поток A держит ресурс 1 и ждёт ресурс 2, поток B держит ресурс 2 и ждёт ресурс 1.

В контексте GCD и Swift deadlock возможен и на одном потоке, но это не классический deadlock, а скорее "self-deadlock": вызов DispatchQueue.main.sync внутри main queue блокирует очередь, которая ждёт саму себя. Формально это тоже circular wait, но с одним участником.

Для senior-позиции важно различать:

  • классический deadlock между несколькими потоками (минимум 2)
  • self-deadlock на одной serial queue (1 поток)
  • livelock и starvation - смежные проблемы, но не deadlock

На практике

В iOS-разработке deadlock чаще всего встречается при неправильном использовании GCD:

  • DispatchQueue.main.sync из main thread
  • вложенные sync вызовы на одной serial queue
  • блокировки NSLock или os_unfair_lock в неправильном порядке

Для диагностики используйте Thread Sanitizer в Xcode - он детектирует data races и deadlocks. Также полезно правило: никогда не вызывайте sync на очереди, которая может быть текущей.

Пример кода

SWIFT
// Классический deadlock с двумя потоками
let lock1 = NSLock()
let lock2 = NSLock()
let threadA = Thread {
lock1.lock()
Thread.sleep(forTimeInterval: 0.1)
lock2.lock() // ждёт lock2, который держит threadB
lock2.unlock()
lock1.unlock()
}
let threadB = Thread {
lock2.lock()
Thread.sleep(forTimeInterval: 0.1)
lock1.lock() // ждёт lock1, который держит threadA
lock1.unlock()
lock2.unlock()
}
threadA.start()
threadB.start()
// Self-deadlock на main queue
DispatchQueue.main.async {
DispatchQueue.main.sync { // deadlock
print("never reached")
}
}

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

Начните с короткого ответа "два потока", затем уточните про self-deadlock на одной очереди. Покажите понимание условий Коффмана. Для senior-уровня добавьте рассуждение о том, почему один поток недостаточен: нет конкуренции за ресурсы, circular wait невозможен. Упомяните, что в iOS с GCD deadlock возможен и на одном потоке, но это исключение из правил.

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

  • понимание базовых концепций конкурентности
  • умение различать классический deadlock и self-deadlock
  • знание GCD и его подводных камней
  • способность рассуждать о причинах и условиях deadlock
  • практический опыт отладки подобных проблем

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

  • ответ "один поток" без пояснения про self-deadlock
  • путаница между deadlock и race condition
  • утверждение, что deadlock возможен без блокировок (это неверно для классического определения)
  • игнорирование контекста GCD при ответе
  • отсутствие упоминания условий Коффмана для senior-уровня

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

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