Event Loop NodeJS

NodeJS отличается от других платформ тем, как обрабатываются события ввода/вывода, или проще I/O. NodeJS рекламируют как “Не блокирующая, событийно управляемая платформа на базе движка JavaScript V8” (да, именно от V-образного 8-цилиндрового двигателя название и было взято).

Что все это значит? Что значит “не блокирующий” и “событийно управляемый”? Ответы на все эти вопросы лежат в основе ядра NodeJS - цикле событий (Event Loop). Это первая статья из серии где будет объяснено что такое Event Loop, как он работает и как это влияет на разработка.

Для начала, любой запрос ввода/вывода должен завершиться и вернуть ответ. Ответом будут запрошенные данные, если запрос успешно выполнился или ошибка, которая сообщит что запрос завершился неудачей. Получение этого ответа и называется событием. Эти события обрабатываются согласно следующему алгоритму:

  1. Event Demultiplexer получает запрос и отправляет его в соответствующую систему.
  2. после обработки ввода / вывода Event Demultiplexer регистрирует обработчики этого события, и, когда придет ответ, необходимый обработчик этого события будет добавлен в очередь событий (Event Queue)
  3. когда в очереди имеются события они обрабатываются в порядке их добавления в очередь
  4. если события больше нет и нет ожидающих ответа запросов, программа завершается

Именно это и есть Event Loop. Он однопоточный и почти бесконечный.

В целом это соответствует паттерну проектирования Reactor Pattern, но Event Loop более сложен, потому как Event demultiplexer это не один компонент, обрабатывающий все события ввода/вывода, да и очередь событий в нем не такая простая как может показаться.

Big structure of event loop

Event Demultiplexer

В реальном мире не существует сущности Event Demultiplexer, это абстракция. В реальном мире он реализован во множестве разных систем, например epoll в Linux, kqueue в BSD системах (macOS), event ports в Solaris, IOCP (Input Output Completion Port) в Windows, и т.д.. NodeJS лишь предоставляет некоторую обертку для работы с ними. Для этого была разработана библиотека libuv, которая реализует большую часть этой абстракции

Event Queue

В NodeJS существует несколько очередей, и в каждую попадают события своего типа

После очередной главной очереди event loop обрабатывает события из двух промежуточных очередей.

Главные очереди, обрабатываемые libuv:

  • очередь таймеров, пополняемая посредством функций setTimeout и setInterval
  • события ввода/вывода
  • очередь, пополняемая посредством функции setImmediate
  • очередь обработчиков событий закрытия

Кроме 4 основных очередей существуют еще 2 очереди микрозадач (microtasks), прежде упомянутые как промежуточные, эти очереди уже являются частью NodeJS а не libuv:

  • Next ticks queue - микрозадачи, добавленные посредством функции process.nextTick
  • Microtasks queue - другие микрозадачи такие как колбеки завершенных промисов

После каждой фазы основного цикла выполняются эти внутренние очереди - сначала очередь next ticks, пока она не окажется пустой, а затем и очередь остальных микрозадач, пока и она не останется пустой.

Если во время выполнения очереди next ticks будут добавлены еще события в очередь next ticks, то они тоже будут выполнены до перехода к другим очередям, это может вызвать IO стагнацию - когда очередь событий наполняется быстрее чем обрабатывается и NodeJS просто не приступит к обработке других очередей событий

Перевод https://blog.insiderattack.net/event-loop-and-the-big-picture-nodejs-event-loop-part-1-1cb67a182810