> Что происходит при состояниях гонки, дедлоках, инверсии приоритетов, взрыве потоков, голодании и лайфлоке (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 = 0func increment() { value += 1 } // data race при вызове из нескольких очередей}// Исправление через actor (Swift 5.5+)actor SafeCounter {private var value = 0func 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 = trueif !b.isLocked {b.isLocked = true// работаa.isLocked = false; b.isLocked = falsebreak} else {a.isLocked = falseusleep(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 как основной инструмент поиска гонок.
- Путать голодание с инверсией приоритетов: голодание - отсутствие прогресса, инверсия - временная блокировка высокоприоритетного потока.
> Похожие задачи по mobile
Как организовать сетевые вызовы в iOS проекте
Может ли дедлок возникать между задачами на разных очередях
Из чего состоит HTTP запрос
Использовали ли паттерны Router и Coordinator
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью