Параллельные вычисления
Асинхронное программирование
2009 год. Ryan Dahl показывает Node.js на JSConf. Демонстрация: скачать файл с диска, пока Apache зависает (блокирующий I/O). Node обрабатывает параллельные запросы. Идея простая: один поток + async I/O вместо потока на запрос. LinkedIn переходит с Ruby on Rails - серверный парк сокращается с 30 до 3 машин.
- Node.js в Netflix обрабатывает 2 миллиарда запросов в день - async I/O позволяет держать высокий RPS на малом количестве инстансов
- Nginx держит C10K (10 000 одновременных соединений) на одном потоке благодаря epoll - Apache на том же железе падает при 1000
- tokio (Rust) и asyncio (Python) используют io_uring на Linux 5.1+ - Cloudflare Workers на Rust обрабатывает 25 миллионов запросов в секунду
async/await: синтаксический сахар над промисами
async/await не добавляет параллелизм - это синтаксис для работы с промисами без callback hell. Функция с async возвращает Promise. await приостанавливает выполнение функции (не потока!), возвращая управление event loop до разрешения промиса.
Promise.all падает при первой ошибке. Promise.allSettled ждёт все промисы и возвращает статус каждого. Promise.race возвращает первый завершившийся (полезно для timeout паттерна). Promise.any - первый успешный.
Что происходит с event loop когда выполняется await?
Event Loop: сердце Node.js
Event loop - бесконечный цикл: проверить очередь макрозадач (setTimeout, I/O callbacks), выполнить, проверить микрозадачи (Promise.then, queueMicrotask), повторить. Libuv - C-библиотека под Node.js - абстрагирует event loop над epoll (Linux), kqueue (macOS), IOCP (Windows).
Node.js Worker Threads (2018) решают проблему CPU-intensive задач. worker_threads.Worker запускает JS в отдельном потоке с собственным event loop. Shared memory через SharedArrayBuffer + Atomics. Используется в Next.js для server-side rendering и в Webpack для параллельной сборки.
После выполнения макрозадачи event loop делает следующее:
io_uring: революция асинхронного I/O в Linux
2019 год. Jens Axboe добавляет в Linux ядро io_uring - принципиально новый API для асинхронного I/O. До этого epoll уведомлял когда дескриптор готов, а само чтение было синхронным системным вызовом. io_uring даёт настоящее async I/O: отдать команду ядру, получить результат когда готово, без системных вызовов в процессе.
io_uring в 3 раза быстрее epoll при большом количестве одновременных операций. liburing используется в nginx, PostgreSQL (начиная с 16), и tokio (Rust async runtime). Node.js 21+ переключился на io_uring для файловых операций на Linux.
Главное преимущество io_uring перед epoll+read:
epoll и multiplexed I/O
epoll - Linux API для мониторинга множества дескрипторов без polling. В отличие от select (O(n) scan), epoll использует event-driven уведомления. Nginx держит 10 000+ соединений на одном потоке именно благодаря epoll.
Edge-triggered (EPOLLET) vs Level-triggered (default): ET уведомляет только при изменении состояния, LT уведомляет пока данные есть. Nginx использует ET для максимальной производительности - нужно читать всё доступное за раз. Ошибка в ET: если не прочитать все данные, следующего уведомления не будет.
Почему nginx использует epoll edge-triggered вместо level-triggered?
Ключевые идеи
- async/await - синтаксис над промисами: await не блокирует поток, а возвращает управление event loop.
- Event loop: микрозадачи (Promise) выполняются перед макрозадачами (setTimeout, I/O). Блокировка loop = стоп все запросы.
- io_uring заменяет epoll: батчинг I/O операций через shared ring buffers, минимум syscalls, 3x throughput при высокой нагрузке.
Связанные темы
Асинхронное программирование пересекается с несколькими концепциями:
- Concurrency на масштабе — Thread pools, backpressure и circuit breakers - дополнение к async I/O при высоких нагрузках
- CSP и каналы (Go) — Go решает ту же задачу иначе - горутины + каналы вместо callbacks и промисов
Вопросы для размышления
- Почему CPU-intensive код (например, crypto.createHash в Node.js) блокирует event loop и что с этим делать?
- В чём разница между concurrency (async/await) и parallelism (Worker Threads) в Node.js?
- Когда async/await в Python с asyncio медленнее обычного threading?