Конкурентность
В Swift две эпохи конкурентности, и на собеседовании спрашивают обе. Старая — императивная: вы вручную раскладываете работу по очередям GCD (DispatchQueue), выбираете sync или async, класс QoS, а общее изменяемое состояние защищаете последовательной очередью или блокировкой. Новая — декларативная: async/await и структурированная конкурентность описывают, что от чего зависит, а компилятор и рантайм сами управляют потоками. Поверх неё акторы сериализуют доступ к своему состоянию, а Sendable и строгая проверка конкурентности ловят гонки данных ещё до запуска.
Сквозная линия у обеих эпох одна — защита общего изменяемого состояния и предсказуемая работа с потоками. Именно здесь спрятаны классические ловушки собеседования: await не блокирует поток, а приостанавливает задачу; DispatchQueue.main.sync с главного потока — мгновенный дедлок; актор реентерабелен, поэтому его инвариант может сломаться на await; а гонка данных и состояние гонки — это не одно и то же. Разберём каждый слой по порядку.
Карта темы
- async/await — структурированная конкурентность, точки приостановки,
Taskи task group'ы,async let, кооперативная отмена иcontinuation'ы для моста со старым кодом. - GCD — очереди (последовательные и параллельные, главная),
syncпротивasync, классыQoS, классический дедлок,DispatchGroupи барьеры, взрывной рост потоков. - Акторы и гонки данных — акторы сериализуют доступ к своему состоянию, реентерабельность,
@MainActor,Sendableи проверки на этапе компиляции,nonisolated, разница гонки данных и состояния гонки.
Частые ошибки и ловушки
| Ошибка | Последствие |
|---|---|
Считать, что await блокирует поток | Неверная модель — задача приостанавливается, поток свободен для другой работы |
DispatchQueue.main.sync с главного потока | Мгновенный дедлок: поток ждёт сам себя |
Обновлять UI не с главного потока/@MainActor | Редкие падения, которые ловит Main Thread Checker |
Думать, что актор защищает инвариант через await | Реентерабельность: состояние могло измениться за время приостановки |
| Считать гонку данных и состояние гонки синонимами | Разные баги — один ловится компилятором, другой нет |
Троттлинг на DispatchSemaphore с блокирующим ожиданием | Взрывной рост потоков и потерянный пул |
Значение для собеседований
Конкурентность проверяют как модель мышления: понимаете ли вы, что происходит с потоками и общим состоянием, или пересказываете рецепты. Кандидат, который говорит «await — это точка приостановки: задача снимается с потока, поток берёт другую работу, а не простаивает», сразу опережает того, кто думает, что await «ждёт, блокируя поток».
Что обычно проверяют:
- Разницу
syncиasyncи почемуmain.syncс главного потока — дедлок. - Почему
awaitне блокирует поток и что такое точка приостановки. - Как акторы устраняют гонки данных и почему реентерабельность всё равно требует осторожности.
- Отличие гонки данных от состояния гонки и что из этого ловит компилятор.
Типичный неверный ответ: «async/await — это просто новый синтаксис поверх GCD, и await блокирует поток, пока не придёт результат». На деле await приостанавливает задачу и освобождает поток, структурированная конкурентность добавляет отмену и связь родитель-потомок, а акторы дают то, чего у GCD не было, — защиту от гонок данных на этапе компиляции.