Какие проблемы с многопоточностью существуют, например race condition, data race, starvation, priority inversion, deadlock
Что проверяют
Различаете ли вы эти проблемы по сути, особенно race condition и data race, и знаете ли, чем их ловить в iOS. Следом спрашивают, какие из проблем снимает Swift Concurrency, а какие остаются.
Как отвечать
Дайте каждой проблеме определение и пример из iOS:
- data race: два потока обращаются к одной памяти без синхронизации, и хотя бы один пишет. Это неопределённое поведение, вплоть до падения на мутации массива или словаря. Ловится Thread Sanitizer, а в строгом режиме Swift 6 многое отсекает компилятор через
Sendableи акторы; - race condition: результат зависит от порядка выполнения, даже если каждое обращение защищено. Пример «проверить, потом действовать»: два потока видят пустой кэш и оба идут в сеть. В акторе то же даёт реентерабельность: после
awaitсостояние могло измениться; - starvation: задача долго не получает процессор или ресурс. Фоновая работа с QoS
.backgroundне продвигается под нагрузкой, а блокирующие вызовы в очередях GCD приводят к thread explosion; - priority inversion: задача с высоким QoS ждёт замок, который держит низкоприоритетная.
os_unfair_lockи мьютекс знают владельца и поднимают его приоритет, аDispatchSemaphoreвладельца не знает, поэтому Thread Performance Checker в Xcode и ругается на такие ожидания; - deadlock: взаимное ожидание. Классика:
DispatchQueue.main.syncс главного потока,syncв ту же последовательную очередь или два замка, взятые в разном порядке.
Решения: последовательная очередь или замок (OSAllocatedUnfairLock, Mutex из модуля Synchronization на iOS 18), акторы для нового кода, единый порядок захвата замков. Диагностика: Thread Sanitizer, Main Thread Checker и шаблон Swift Concurrency в Instruments.
Где спотыкаются
Считают race condition и data race одним и тем же. Актор устраняет data race, но логическую гонку через реентерабельность оставляет.
Советуют «сделать свойство atomic». В Swift такого модификатора нет, это Objective-C, и даже там атомарный геттер не делает составную операцию безопасной.
Ждут результата Task через DispatchSemaphore внутри кооперативного пула. Пул ограничен числом ядер, и так легко получить deadlock.