Добавить объявление

Swift Concurrency: что на самом деле делает компилятор?

Swift Concurrency - это не просто синтаксический сахар. Под капотом происходит сложная трансформация кода, управляемая компилятором и рантаймом. Давайте разберём ключевые компоненты системы.

Компилятор и State Machine:


Когда компилятор встречает async функцию, он разбивает ее на состояния (state machine). Каждый await становится точкой приостановки, между которыми код делится на блоки.

func fetchData() async -> Data {
let data = await downloadData()
let processedData = await process(data)
return processedData
}

Suspension Points (Точки приостановки):


Каждый await это место, где:

  • Стек сохраняется в кучу (heap).

  • Поток освобождается для других задач.

  • Создаётся continuation (информация для возобновления).

Особенности:

  • Приостановка не блокирует поток (в отличие от DispatchQueue).

  • Swift Runtime управляет жизненным циклом данных.

Continuation (Продолжение):


Это объект, который хранит:

  • Точку возобновления (какой await был последним).

  • Локальные переменные.

  • Что делать после получения результата.

Пример:

withCheckedContinuation { continuation in
someAsyncOperation { result in
continuation.resume(returning: result)
}
}

Executor (Исполнитель):


Определяет где и когда выполнять код после await. Могут быть следующие варианты:

  • Главным потоком (для MainActor).

  • Глобальным исполнителем (для обычных async функций).

  • Кастомным (для акторов).

Особенности:

  • Если функция запущена на MainActor, продолжение тоже выполнится на нём.

  • Глобальные функции используют кооперативный пул потоков.

Co-operative Thread Pool (Пул потоков):


Особенности:

  • Фиксированное количество потоков (обычно = числу ядер CPU).

  • Потоки не блокируются: при await они переключаются на другие задачи.

  • Управляется Swift Runtime, а не операционной системой.


Почему это эффективно:

  • Нет накладных расходов на создание потоков.

  • Автоматическая балансировка нагрузки.

Как это работает вместе?


  • Мы вызываем async функцию, после чего компилятор создаёт state machine.

  • При первом await: состояние и переменные сохраняются в continuation, поток возвращается в пул.

  • Когда результат готов, executor решает, где возобновить выполнение.

  • State machine продолжает работу с последней точки.

Пример с потоками:

func loadData() async {
// 1. Выполняется на случайном потоке из пула
let data = await fetchFromNetwork() // 2. Поток освобождается
// 3. Возобновляется на том же или другом потоке
await MainActor.run {
self.data = data // 4. Гарантированно на главном потоке
}
}

Вывод:


  • Компилятор разбивает код на состояния.

  • Continuation хранит прогресс выполнения.

  • Executor выбирает поток для возобновления.

  • Пул потоков обеспечивает эффективное использование CPU.

Это делает Swift Concurrency легковесным и масштабируемым, в отличие от традиционных потоков.
01.10.2025 11 462