Разберем как на самом деле работают потоки в
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 поможет писать более эффективный и надежный код.