> Что происходит при состояниях гонки, дедлоках, инверсии приоритетов, взрыве потоков, голодании и лайфлоке (iOS, Swift)

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

Компании: Bip.ru

Стек: iOS, Swift

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

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

Гонка данных - одновременный доступ к общему ресурсу без синхронизации, приводящий к непредсказуемому состоянию. Дедлок - взаимная блокировка потоков, ожидающих ресурсы друг друга. Инверсия приоритетов - высокоприоритетный поток ждёт низкоприоритетный, захвативший мьютекс. Взрыв потоков - неконтролируемое создание потоков, истощающее ресурсы. Голодание - поток не получает ресурс из-за несправедливого планировщика. Лайфлок - потоки активно меняют состояния, но не прогрессируют. Все это - проблемы конкурентности, критичные для iOS-приложений.

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

Гонка данных (data race) возникает, когда два или более потока обращаются к одной памяти, и хотя бы один пишет, без синхронизации. В Swift это проявляется при работе с mutable state из разных очередей. Последствия - краши, повреждение данных, недетерминированное поведение. Типичный пример - общий массив, изменяемый из main и background queue.

Дедлок (deadlock) - ситуация, когда каждый поток держит ресурс и ждёт ресурс, удерживаемый другим. В GCD часто возникает при вызове DispatchQueue.main.sync из main queue или при вложенных sync на serial queue. Классические условия Коффмана: mutual exclusion, hold and wait, no preemption, circular wait.

Инверсия приоритетов (priority inversion) - низкоприоритетный поток держит мьютекс, нужный высокоприоритетному. Высокоприоритетный блокируется, средний приоритет вытесняет низкий, усугубляя проблему. В iOS актуально для NSLock, os_unfair_lock, операций с QualityOfService. Решение - priority inheritance (как в pthread_mutex с атрибутом PTHREAD_PRIO_INHERIT) или использование os_unfair_lock (не поддерживает наследование, но быстрее).

Взрыв потоков (thread explosion) - создание тысяч потоков, каждый потребляет ~512 КБ стека (на iOS). Приводит к OOM, деградации производительности, зависаниям. Часто возникает при неправильном использовании Thread, DispatchGroup с большим числом параллельных задач, или при рекурсивном создании очередей.

Голодание (starvation) - поток не получает доступ к ресурсу из-за несправедливого планирования. В GCD - когда низкоприоритетные задачи никогда не выполняются, если постоянно приходят высокоприоритетные. В Swift Concurrency - если actor постоянно занят задачами с высоким приоритетом, задачи с низким могут голодать.

Лайфлок (livelock) - потоки не заблокированы, но бесконечно реагируют друг на друга, меняя состояния, без реального прогресса. Пример - два потока пытаются захватить два ресурса в разном порядке, при неудаче отпускают и повторяют, создавая цикл. Отличается от дедлока тем, что потоки активны.

На практике

В iOS-разработке эти проблемы проявляются в конкретных сценариях:

  • Гонки - обновление UI из фоновой очереди, общий кэш, разделяемые модели данных.
  • Дедлоки - DispatchQueue.main.sync из main, semaphore.wait() внутри main queue, вложенные sync на serial queue.
  • Инверсия приоритетов - работа с URLSession и приоритетами задач, OperationQueue с зависимостями.
  • Взрыв потоков - создание Thread на каждый запрос, DispatchQueue.concurrentPerform с огромным числом итераций.
  • Голодание - использование DispatchQueue.global(qos: .utility) при постоянном потоке .userInteractive задач.
  • Лайфлок - ретраи сетевых запросов с взаимной блокировкой, алгоритмы распределённых блокировок.

Инструменты диагностики: Thread Sanitizer (TSan) для гонок, Instruments (Thread States, Time Profiler) для дедлоков и голодания, os_signpost для отслеживания прогресса.

Пример кода

SWIFT
// Гонка данных
final class Counter {
private var value = 0
func increment() { value += 1 } // data race при вызове из нескольких очередей
}
// Исправление через actor (Swift 5.5+)
actor SafeCounter {
private var value = 0
func increment() { value += 1 }
}
// Дедлок
DispatchQueue.main.sync { // deadlock: main queue ждёт саму себя
print("never")
}
// Инверсия приоритетов
let lock = NSLock()
DispatchQueue.global(qos: .utility).async {
lock.lock()
// долгая работа
lock.unlock()
}
DispatchQueue.global(qos: .userInteractive).async {
lock.lock() // блокируется из-за низкоприоритетного потока
}
// Взрыв потоков
for _ in 0..<10000 {
Thread { /* работа */ }.start() // 10000 потоков - OOM
}
// Лайфлок
final class Resource {
var isLocked = false
}
let a = Resource(), b = Resource()
DispatchQueue.global().async {
while true {
if !a.isLocked {
a.isLocked = true
if !b.isLocked {
b.isLocked = true
// работа
a.isLocked = false; b.isLocked = false
break
} else {
a.isLocked = false
usleep(100)
}
}
}
}
// аналогичный цикл для b - возможен livelock

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

Начни с краткого определения каждого термина, затем переходи к примерам из iOS. Покажи понимание инструментов: TSan, DispatchQueue, actor, os_unfair_lock. Для senior важно упомянуть trade-off между производительностью и безопасностью, а также современные подходы - Swift Concurrency (actors, task groups) как способ уменьшить количество гонок и дедлоков.

При ответе структурируй: определение → причина → последствия → решение. Для каждого термина приведи реальный сценарий из мобильной разработки. Если спросят про конкретный инструмент - объясни, как диагностировать проблему.

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

  • Понимание фундаментальных проблем конкурентности, а не заучивание терминов.
  • Умение применять знания к iOS-специфике: GCD, OperationQueue, Swift Concurrency.
  • Знание инструментов диагностики и профилирования.
  • Способность предложить практические решения: синхронизация, избегание вложенных блокировок, использование actors.
  • Понимание trade-off: например, os_unfair_lock быстрее, но не поддерживает priority inheritance.

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

  • Путать дедлок и лайфлок: в лайфлоке потоки активны, в дедлоке - заблокированы.
  • Считать, что DispatchQueue.main.sync безопасен, если вызывается не из main - на самом деле это частая причина дедлоков.
  • Игнорировать инверсию приоритетов, считая её редкой - на iOS она реальна при работе с NSLock и QoS.
  • Предлагать DispatchQueue.serial как панацею от гонок, забывая про вложенные вызовы и reentrancy.
  • Не упоминать Thread Sanitizer как основной инструмент поиска гонок.
  • Путать голодание с инверсией приоритетов: голодание - отсутствие прогресса, инверсия - временная блокировка высокоприоритетного потока.

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

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