Как избежать гонки потоков
Что проверяют
Отличаете ли вы гонку данных (data race) от логической гонки (race condition) и знаете ли инструменты Swift, которые убирают первую ещё на этапе компиляции. Следом почти всегда спрашивают про акторы и их реентерабельность или просят найти гонку в коротком куске кода.
Как отвечать
Начните с определения: гонка данных возникает, когда два потока обращаются к одной памяти без синхронизации и хотя бы один из них пишет. Дальше перечислите способы, от современных к классическим:
- акторы:
actorсериализует доступ к своему состоянию, снаружи к нему обращаются только черезawait.@MainActorзакрепляет код за главным потоком; - строгая проверка конкурентности: в языковом режиме Swift 6 компилятор требует
Sendableдля данных, пересекающих границу изоляции, и многие гонки ловятся до запуска; - последовательная очередь GCD:
DispatchQueueбез атрибута.concurrentвыполняет задачи по одной. Это не значит «в одном потоке»: поток может меняться, гарантирована только очерёдность; - барьер на конкурентной очереди: чтения идут параллельно через
sync, запись черезasync(flags: .barrier). Классическая схема «много читателей, один писатель»; - блокировки:
NSLock,OSAllocatedUnfairLockс iOS 16,Mutexиз фреймворка Synchronization с iOS 18. Критическая секция должна быть короткой, без сетевых вызовов внутри.
Обязательно назовите способ проверки: Thread Sanitizer включается в схеме Xcode на вкладке Diagnostics и показывает конкретные строки, где встретились два потока.
Где спотыкаются
Считают, что актор решает всё. Внутри метода актора после каждого await состояние могло поменяться другим вызовом, поэтому проверку и запись нельзя разрывать ожиданием.
Защищают только запись. Чтение без той же блокировки остаётся гонкой, даже если пишет всего один поток.
Вызывают sync на той же последовательной очереди, в которой уже выполняется код. Это взаимная блокировка, и в отладке приложение обычно сразу падает с ошибкой libdispatch.