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

Swift Concurrency: как Executors и Actors управляют потоками

Разберем как на самом деле работают потоки в Swift Concurrency. Если вы думаете, что async/await - это просто синтаксический сахар, приготовьтесь удивляться. Под капотом скрывается мощная система управления задачами через Executors и Actors.

Из чего состоит Swift Concurrency:


  • Task: единица асинхронной работы (как поток для синхронных функций).

  • Job: часть задачи между точками await (suspension points).

  • Executor: планировщик, который распределяет Job по потокам.

  • Cooperative Thread Pool: пул потоков (число = числу ядер устройства).

Типы Executors:


  • Global Concurrent Executor: дефолтный, распределяет задачи по потокам из пула.

  • Serial Executors: для акторов, выполняет задачи последовательно.

  • Main Actor Executor: специальный для главного потока.

Пример кастомного Executor:


// Создаем свой планировщик
final class CustomExecutor: TaskExecutor {
func enqueue(_ job: consuming ExecutorJob) {
print("Запускаем задачу на потоке: \(Thread.current)")
job.runSynchronously(on: asUnownedTaskExecutor())
}

func asUnownedTaskExecutor() -> UnownedTaskExecutor {
UnownedTaskExecutor(ordinary: self)
}
}

// Использование
Task(executorPreference: CustomExecutor()) {
print("Выполняем работу")
try await Task.sleep(for: .seconds(1))
print("Завершаем работу")
}

Actors - безопасность данных:


Акторы защищают от гонки данных, выполняя методы последовательно:

actor SafeCounter {
private var count = 0

func increment() {
count += 1
print("Текущее значение: \(count)")
}
}

// Использование
let counter = SafeCounter()

Task {
await counter.increment() // Безопасный доступ
}

Как работает последовательность:


Даже при параллельных вызовах актор гарантирует порядок:

await withTaskGroup { group in
for i in 0..<5 {
group.addTask { await counter.increment() }
}
}
// Вывод всегда будет: 1, 2, 3, 4, 5

Global Actors:


@MainActor вы уже знаете, но можно создавать и свои глобальные акторы:

@globalActor
actor NetworkActor {
static let shared = NetworkActor() // Единый экземпляр на все приложение
}

@NetworkActor
func fetchData() async -> Data {
// Все вызовы этого метода будут последовательными
// даже из разных частей приложения!
}

@NetworkActor
class ApiService {
// Все методы класса будут автоматически изолированы
func getUser() async -> User {
}
}

Зачем это нужно, спросите вы. Чтобы разные функции и классы использовали один общий актор. Например:

  • Все сетевые запросы через один NetworkActor.

  • Все операции с базой данных через DatabaseActor.

  • Все аналитические события через AnalyticsActor.

Важные особенности:


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

  • Автоматическое планирование: система сама выбирает оптимальный поток.

  • Безопасность по умолчанию: компилятор следит за изоляцией данных.

Когда использовать:


  • Global Concurrent: для независимых задач.

  • Serial Executor: когда важен порядок выполнения.

  • Main Actor: для работы с UI.

  • Custom Executors: для специальных требований к планированию.

Вывод:


Swift Concurrency это не просто async/await, а целая экосистема для безопасной многопоточности. Понимание работы Executors и Actors поможет писать более эффективный и надежный код.
28.09.2025 12 458