Теория языков программирования

Async/await и корутины

Node.js обслуживает 1 миллион соединений на одном процессе. Rust tokio - миллионы concurrent requests на нескольких CPU. Всё это на корутинах и cooperative scheduling. Один паттерн, революционный результат.

  • **Discord**: перешёл с Python asyncio на Rust tokio для голосовых серверов - обрабатывает 1M+ голосовых соединений с задержкой <10ms
  • **AWS Lambda SnapStart**: Java Loom virtual threads позволяют использовать blocking IO без overhead - упрощает serverless код без потери масштабируемости
  • **Android Kotlin coroutines**: Google рекомендует coroutines как primary async solution - заменили RxJava в большинстве Android проектов

Корутины

Корутина - функция, выполнение которой можно приостановить и возобновить позже. В отличие от потока, приостановка корутины кооперативная (явный yield/await), а не вытесняющая (планировщик ОС). Одна корутина занимает ~1KB стека vs ~1MB для OS thread.

Kotlin coroutines компилируются в state machines - нет overhead от OS threads. 100k корутин vs 100k threads: RAM в ~1000x меньше, нет context switch overhead (~1-10μs на переключение).

В чём принципиальное отличие корутины от OS thread?

Async как State Machine

Компилятор трансформирует async функцию в state machine. Каждый `await` - переход между состояниями. Функция 'вспоминает' где остановилась через enum с полями - локальные переменные сохраняются между вызовами.

Почему async функция в Rust не требует отдельного OS thread?

Cooperative Scheduling

Cooperative scheduling - корутины явно уступают управление через await/yield. Runtime (executor/event loop) запускает другие корутины пока текущая ждёт IO. Node.js event loop, tokio (Rust), asyncio (Python) - реализации cooperative scheduling.

Почему CPU-intensive задачи опасны в cooperative scheduling?

Structured Concurrency

Structured concurrency - принцип: дочерние задачи не переживают родительский scope. Если родитель завершается/бросает исключение - все дочерние отменяются. Kotlin coroutineScope, Swift async let, Java Loom StructuredTaskScope.

async/await решает все проблемы concurrency - можно просто добавить async к каждой функции

async/await решает IO concurrency. CPU-bound код, shared mutable state, race conditions - требуют дополнительных механизмов

async/await = cooperative scheduling для IO waiting. CPU-bound -> worker pool. Shared state -> Mutex/Channel. Отменять задачи -> structured concurrency + cancellation. Каждая проблема требует своего инструмента

Что гарантирует structured concurrency?

Итоги

  • **Корутины**: ~1KB стека, cooperative suspension через await. 100K корутин vs 100K threads: в 1000x меньше RAM
  • **State machine**: компилятор трансформирует async функцию. Каждый await = переход состояния. Локальные переменные сохраняются в enum
  • **Cooperative scheduling**: await = точка уступки. CPU-bound код без await блокирует event loop - решение через thread pool
  • **Structured concurrency**: дочерние задачи не переживают scope. Отмена каскадируется. Нет orphan корутин и утечек

Связанные темы

Async/await связан с более глубокими концепциями:

  • Алгебраические эффекты — Async/await - специальный случай algebraic effect handler для Async эффекта
  • Модели памяти — Concurrent корутины с shared state требуют знания memory ordering

Вопросы для размышления

  • Rust Future<Output=T> - нулевая стоимость без runtime. Go goroutines - встроенный runtime с scheduler. Какой подход лучше и почему оба популярны?
  • Structured concurrency решает проблему orphan tasks. Как она соотносится с RAII в Rust - оба гарантируют cleanup при выходе из scope?
  • JavaScript однопоточный, но Node.js обслуживает тысячи concurrent requests. Объясните это не-программисту через аналогию из реального мира.

Связанные уроки

  • dist-03-fallacies
Async/await и корутины

0

1

Войти