Параллельные вычисления

Асинхронное программирование

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?

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

  • os-03-threads
Асинхронное программирование

0

1

Войти