Nodejs Event Loop

Существуют ли внутри двух циклов событий в архитектуре nodejs?

  • libev/libuv
  • v8 цикл событий javascript

В запросе ввода/вывода node очередь запроса на libeio, который, в свою очередь, уведомляет о доступности данных через события с использованием libev и, наконец, эти события обрабатываются циклом событий v8 с использованием обратных вызовов?

В принципе, как libev и libeio интегрированы в архитектуру nodejs?

Есть ли какая-либо документация, позволяющая дать четкую картину внутренней архитектуры nodejs?

Ответ 1

Я лично читал исходный код node.js & v8.

Я столкнулся с подобной проблемой, как вы, когда попытался понять архитектуру node.js для написания собственных модулей.

То, что я публикую здесь, - это мое понимание node.js, и это может быть немного не в порядке.

  1. Libev - это цикл событий, который фактически выполняется внутри node.js для выполнения простых операций цикла событий. Первоначально написано для * nix систем. Libev предоставляет простой, но оптимизированный цикл событий для запуска процесса. Вы можете прочитать больше о libev здесь.

  2. LibEio - это библиотека для асинхронного ввода-вывода. Он обрабатывает дескрипторы файлов, обработчики данных, сокеты и т.д. Подробнее об этом можно прочитать здесь.

  3. LibUv - это уровень абстракции на вершине libeio, libev, c-ares (для DNS) и iocp (для windows asynchronous-io). LibUv выполняет, поддерживает и управляет всеми операциями ввода-вывода и событиями в пуле событий. (в случае освобождения резерва). Вы должны проверить учебник Райана Даля на libUv. Это станет более понятным для вас о том, как работает libUv, а затем вы поймете, как работает node.js поверх libuv и v8.

Чтобы понять только цикл событий javascript, вы должны рассмотреть эти видео

Чтобы увидеть, как libeio используется с node.js для создания асинхронных модулей, вы должны увидеть этот пример.

По сути, внутри node.js происходит то, что цикл v8 выполняется и обрабатывает все части javascript, а также модули C++ [когда они работают в основном потоке (согласно официальной документации, node.js является однопоточным)]. Когда вне основного потока, libev и libeio обрабатывают его в пуле потоков, а libev обеспечивает взаимодействие с основным циклом. Итак, насколько я понимаю, у node.js есть 1 постоянный цикл обработки событий: это цикл обработки событий v8. Для обработки асинхронных задач C++ используется пул потоков [via libeio & libev].

Например:

eio_custom(Task,FLAG,AfterTask,Eio_REQUEST);

То, что появляется во всех модулях, обычно вызывает функцию Task в пуле потоков. По завершении он вызывает функцию AfterTask в основном потоке. Принимая во внимание, что Eio_REQUEST является обработчиком запросов, который может быть структурой/объектом, мотивом которого является обеспечение связи между пулом потоков и основным потоком.

Ответ 2

Похоже, что некоторые из обсуждаемых сущностей (например, libev и т.д.) потеряли актуальность из-за того, что прошло какое-то время, но я думаю, что этот вопрос по-прежнему имеет большой потенциал.

Позвольте мне объяснить работу управляемой событиями модели с помощью абстрактного примера в абстрактной среде UNIX в контексте Node на сегодняшний день.

Программная перспектива:

  • Script двигатель запускает выполнение script.
  • В любой момент, когда встречается операция с привязкой к процессору, она выполняется встроенной (реальной машиной) в своей полноте.
  • Каждый раз, когда встречается связанная операция ввода-вывода, запрос и его обработчик завершения регистрируются с помощью "механизма событий" (виртуальной машины)
  • Повторите операции таким же образом, как до конца script. Операция с привязкой к ЦП - выполнение встроенных, связанных с вводом-выводом запросов, запрос на оборудование, как указано выше.
  • Когда ввод/вывод завершается, слушатели возвращаются.

Механизм событий выше называется инфраструктурой цикла событий Libuv AKA. Node использует эту библиотеку для реализации модели программирования, управляемой событиями.

Node:

  • Имейте один поток для размещения среды выполнения.
  • Поднимите пользователя script.
  • Скомпилируйте его в native [рычаг v8]
  • Загрузите двоичный файл и перейдите в точку входа.
  • Скомпилированный код выполняет связанные с CPU операции в режиме on-line, используя примитивы программирования.
  • Многие коды ввода-вывода и таймера имеют собственные оболочки. Например, сетевой ввод-вывод.
  • Таким образом, вызовы ввода/вывода маршрутизируются с мостов script на С++, причем дескриптор ввода-вывода и обработчик завершения передаются как аргументы.
  • Нативный код выполняет цикл libuv. Он получает цикл, помещает событие низкого уровня, которое представляет собой ввод-вывод, и встроенную оболочку обратного вызова в структуру цикла libuv.
  • Нативный код возвращается к script - никаких операций ввода-вывода не происходит в настоящий момент!
  • Элементы выше повторяются много раз, пока не будет выполнен весь код не-ввода-вывода, и весь код ввода-вывода будет зарегистрирован libuv.
  • Наконец, когда в системе ничего не осталось, Node передать элемент управления libuv
  • libuv вступает в действие, он выбирает все зарегистрированные события, запрашивает операционную систему, чтобы получить работоспособность.
  • Те, которые готовы для ввода-вывода в неблокирующем режиме, подбираются, выполняются операции ввода-вывода и выходят их обратные вызовы. Один за другим.
  • Те, которые еще не готовы (например, прочитанный сокет, для которого еще не написана другая конечная точка), будут продолжать изучаться с ОС до тех пор, пока они не будут доступны.
  • Цикл внутренне поддерживает постоянно увеличивающийся таймер. Когда приложение запрашивает отложенный ответ (например, setTimeout), это значение внутреннего таймера используется для вычисления правильного времени для запуска обратного вызова.

В то время как большинство функциональных возможностей удовлетворяются таким образом, некоторые (асинхронные версии) файловых операций выполняются с помощью дополнительных потоков, хорошо интегрированных в libuv. В то время как операции сетевого ввода-вывода могут ждать в ожидании внешнего события, такого как другая конечная точка, отвечающая данными и т.д. Операции с файлами требуют некоторой работы от самого Node. Например, если вы откроете файл и дождитесь, когда fd будет готов с данными, это не произойдет, поскольку никто не читает на самом деле! В то же время, если вы читаете файл inline в основном потоке, он может потенциально блокировать другие действия в программе и может создавать видимые проблемы, поскольку файловые операции очень медленные по сравнению с связанными с процессором операциями. Таким образом, внутренние рабочие потоки (настраиваемые через переменную окружения UV_THREADPOOL_SIZE) используются для работы с файлами, а абстракция, управляемая событиями, остается неповрежденной с точки зрения программы.

Надеюсь, что это поможет.

Ответ 3

Введение в libuv

Проект node.js начался в 2009 году, когда среда JavaScript была отделена от браузера. Используя Googles V8 и Marc Lehmanns libev, node.js объединил модель I/O - Evented - с языком, который хорошо подходил для стиля программирования; из-за того, как он был сформирован браузерами. По мере роста популярности node.js важно было заставить его работать в Windows, но libev работал только в Unix. В Windows эквивалентом механизмов уведомления о событиях ядра, таких как kqueue или (e) poll, является IOCP. libuv был абстракцией вокруг libev или IOCP в зависимости от платформы, предоставляя пользователям API на основе libev. В node-v0.9.0 версия libuv libev была удалена.

Также одна картинка, которая описывает цикл событий в Node.js @BusyRich


Обновление 05/09/2017

В этом цикле событий документа Node.js,

Следующая диаграмма показывает упрощенный обзор порядка операций цикла событий.

┌───────────────────────┐ ┌─>│ timers │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ │ │ I/O callbacks │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ │ │ idle, prepare │ │ └──────────┬────────────┘ ┌───────────────┐ │ ┌──────────┴────────────┐ │ incoming: │ │ │ poll │<─────┤ connections, │ │ └──────────┬────────────┘ │ data, etc. │ │ ┌──────────┴────────────┐ └───────────────┘ │ │ check │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ └──┤ close callbacks │ └───────────────────────┘

примечание: каждое поле будет упоминаться как "фаза" цикла событий.

Обзор фаз

  • таймеры: эта фаза выполняет обратные вызовы, запланированные setTimeout() и setInterval().
  • Обратные вызовы ввода/вывода: выполняет почти все обратные вызовы, за исключением
  • закройте обратные вызовы, запланированные таймерами, и setImmediate(). на холостом ходу, подготовка: используется только для внутреннего использования.
  • опрос: получить новые события ввода/вывода; узел будет блокировать здесь, когда это необходимо.
  • check: setImmediate() обратные вызовы вызываются здесь.
  • Закрыть обратные вызовы: например, socket.on('close',...).

Между каждым запуском цикла обработки событий Node.js проверяет, ожидает ли он каких-либо асинхронных операций ввода-вывода или таймеров, и корректно завершает работу, если их нет.

Ответ 4

В архитектуре NodeJs существует один цикл событий.

Node.js Модель цикла событий

Node приложения выполняются в однопоточной модели, управляемой событиями. Однако Node реализует пул потоков в фоновом режиме, чтобы можно было выполнить работу.

Node.js добавляет работу в очередь событий, а затем имеет один поток, запускающий цикл событий, забирает его. Цикл событий захватывает верхний элемент в очереди событий, выполняет его, а затем захватывает следующий элемент.

При выполнении кода, который более долговечен или блокирует ввод-вывод, вместо прямого вызова функции он добавляет функцию в очередь событий вместе с обратным вызовом, который будет выполнен после завершения функции. Когда все события в очереди событий Node.js выполнены, приложение Node.js завершается.

Цикл событий начинает сталкиваться с проблемами, когда наши функции приложения блокируются при вводе-выводе.

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

Когда событие, которое блокирует ввод-вывод, извлекается из очереди событий, Node.js извлекает поток из пула потоков и выполняет там функцию вместо потока потока основного события. Это предотвращает блокировку ввода-вывода от остальной части событий в очереди событий.

Ответ 5

Существует только один цикл событий, предоставляемый libuv, V8 - всего лишь механизм выполнения JS.

Ответ 6

Как новичок в JavaScript, я также сомневался, содержит ли NodeJS 2 цикла событий? После долгих исследований и обсуждений с одним из участников V8 я получил следующие концепции.

  • Цикл событий является фундаментальной абстрактной концепцией модели программирования JavaScript. Таким образом, механизм V8 обеспечивает реализацию по умолчанию для цикла событий, который может заменять или расширять устройства для внедрения (браузер, узел). Ребята, вы можете найти реализацию цикла событий по умолчанию в V8 здесь
  • В NodeJS существует только один цикл обработки событий, который предоставляется средой выполнения узла. Реализация цикла событий V8 по умолчанию была заменена реализацией цикла событий NodeJS

Ответ 7

Функция pbkdf2 имеет реализацию JavaScript, но фактически делегирует всю работу, которая должна быть выполнена стороне C++.

env->SetMethod(target, "pbkdf2", PBKDF2);
  env->SetMethod(target, "generateKeyPairRSA", GenerateKeyPairRSA);
  env->SetMethod(target, "generateKeyPairDSA", GenerateKeyPairDSA);
  env->SetMethod(target, "generateKeyPairEC", GenerateKeyPairEC);
  NODE_DEFINE_CONSTANT(target, OPENSSL_EC_NAMED_CURVE);
  NODE_DEFINE_CONSTANT(target, OPENSSL_EC_EXPLICIT_CURVE);
  NODE_DEFINE_CONSTANT(target, kKeyEncodingPKCS1);
  NODE_DEFINE_CONSTANT(target, kKeyEncodingPKCS8);
  NODE_DEFINE_CONSTANT(target, kKeyEncodingSPKI);
  NODE_DEFINE_CONSTANT(target, kKeyEncodingSEC1);
  NODE_DEFINE_CONSTANT(target, kKeyFormatDER);
  NODE_DEFINE_CONSTANT(target, kKeyFormatPEM);
  NODE_DEFINE_CONSTANT(target, kKeyTypeSecret);
  NODE_DEFINE_CONSTANT(target, kKeyTypePublic);
  NODE_DEFINE_CONSTANT(target, kKeyTypePrivate);
  env->SetMethod(target, "randomBytes", RandomBytes);
  env->SetMethodNoSideEffect(target, "timingSafeEqual", TimingSafeEqual);
  env->SetMethodNoSideEffect(target, "getSSLCiphers", GetSSLCiphers);
  env->SetMethodNoSideEffect(target, "getCiphers", GetCiphers);
  env->SetMethodNoSideEffect(target, "getHashes", GetHashes);
  env->SetMethodNoSideEffect(target, "getCurves", GetCurves);
  env->SetMethod(target, "publicEncrypt",
                 PublicKeyCipher::Cipher<PublicKeyCipher::kPublic,
                                         EVP_PKEY_encrypt_init,
                                         EVP_PKEY_encrypt>);
  env->SetMethod(target, "privateDecrypt",
                 PublicKeyCipher::Cipher<PublicKeyCipher::kPrivate,
                                         EVP_PKEY_decrypt_init,
                                         EVP_PKEY_decrypt>);
  env->SetMethod(target, "privateEncrypt",
                 PublicKeyCipher::Cipher<PublicKeyCipher::kPrivate,
                                         EVP_PKEY_sign_init,
                                         EVP_PKEY_sign>);
  env->SetMethod(target, "publicDecrypt",
                 PublicKeyCipher::Cipher<PublicKeyCipher::kPublic,
                                         EVP_PKEY_verify_recover_init,
                                         EVP_PKEY_verify_recover>);

ресурс: https://github.com/nodejs/node/blob/master/src/node_crypto.cc

Модуль Libuv несет еще одну ответственность, которая имеет отношение к некоторым очень специфическим функциям в стандартной библиотеке.

Для некоторых вызовов стандартных функций библиотеки сторона Node C++ и Libuv решают полностью выполнить дорогостоящие вычисления вне цикла событий.

Вместо этого они используют нечто, называемое пулом потоков, пул потоков представляет собой серию из четырех потоков, которые можно использовать для выполнения вычислительно дорогостоящих задач, таких как функция pbkdf2.

По умолчанию Libuv создает 4 потока в этом пуле потоков.

В дополнение к потокам, используемым в цикле событий, есть четыре других потока, которые можно использовать для разгрузки дорогих вычислений, которые должны выполняться внутри нашего приложения.

Многие функции, включенные в стандартную библиотеку Node, автоматически используют этот пул потоков. Функция pbkdf2 является одной из них.

Наличие этого пула потоков очень важно.

Таким образом, Node не является однопоточным, поскольку существуют другие потоки, которые Node использует для выполнения некоторых вычислительно дорогостоящих задач.

Если пул событий отвечал за выполнение вычислительно дорогостоящей задачи, то наше приложение Node больше ничего не могло сделать.

Наш процессор выполняет все инструкции внутри потока одну за другой.

Используя пул потоков, мы можем делать другие вещи внутри цикла событий во время выполнения вычислений.