В первой части этой статьи рассматривается асинхронная природа JavaScript и то, как JavaScript обрабатывает асинхронные задачи с использованием веб-API и очереди сообщений. Хотя отличий немного, ключевые идеи в реализации цикла обработки событий Node JS остаются теми же. Здесь мы рассмотрим некоторые ключевые различия в том, как Node обращается с циклом событий.
Однако, в отличие от учебных пособий по JavaScript, учебные пособия по Node JS заранее сообщают, что Node является однопоточным, что является стандартным лозунгом любой книги/учебника/МООК о Node JS. Однако это утверждение следует тщательно изучить.
Если вы посмотрите на правую часть изображения выше, вы увидите стек рабочих потоков, доступных во время выполнения Node. Что ставит под сомнение распространенное мнение об однопоточном характере Node JS.
По сути, Node JS — это не новый язык программирования или другой фреймворк/библиотека JS. Это среда выполнения, которая дает JS доступ для выполнения общих действий, которые делают другие языки программирования, таких как доступ к файловой системе, ввод-вывод, работа в сети и т. д. Но прежде чем мы углубимся в сорняки, я хотел бы поговорить о двух фундаментальных элементах архитектуры узлов.
- V8 Engine — это движок JavaScript с открытым исходным кодом, созданный Google, который компилирует код JS в C++. Именно это позволяет получить доступ к JS-коду вне браузера.
- Libuv — это также проект с открытым исходным кодом, написанный на C++, который предоставляет Node доступ к операционным системам, базовой файловой системе, доступ к сети, а также обрабатывает некоторые аспекты параллелизма.
Цикл событий узла
Давайте посмотрим на более высокий уровень реализации цикла обработки событий Node JS. В отличие от цикла событий JS, Node выполняет 3 задачи в определенном порядке.
- Node проверяет, есть ли ожидающие таймеры. Которые устанавливаются с помощью таких функций, как setTimeout и setInterval.
- Затем он проверяет, остались ли незавершенные задачи ОС. Сервер, прослушивающий порт, является одним из них.
- Наконец, он проверяет, выполняются ли за кулисами какие-либо ожидающие длительные операции, такие как файловый ввод-вывод.
Node приостанавливает выполнение на этом этапе и продолжает, когда
- выполнена новая незавершенная задача
- выполнена новая ожидающая операция
- таймер вот-вот завершится
Важно отметить, что приложение узла фактически завершается, когда цикл обработки событий не может найти ожидающих выполнения задач.
Является ли Node JS однопоточным?
Проще говоря, цикл событий Node — это однопоточный. Тем не менее, некоторые изплатформ Node/стандартной библиотеки НЕоднопоточные. Некоторые модули узлов, такие как fs (файловая система), crypto (криптографические функции), имеют функции, использующие несколько потоков. Давайте посмотрим на пример кода, чтобы закрепить эти новые знания.
Приведенный выше код довольно прост, поскольку он хеширует строку с помощью мощного алгоритма «sha512» и записывает время, затраченное на это.
Чтобы поэкспериментировать с многопоточным характером этой программы, вы можете закомментировать последние 4 вызова и запустить только первый вызов. Это регистрирует на моей машине 963 мс времени. Это означает, что для этого шифрования требуется около 1 с.
После этого я закомментировал только последний вызов и запустил первые 4 вызова. Вот результат.
Вы можете видеть, что все процессы заняли примерно около 1 секунды. Если бы этот процесс был однопоточным, это должно было занять около 4 секунд! После этого я выполнил все 5 вызовов хеширования. Что дает интересный результат.
На этот раз код занял почти 2 секунды, что в два раза превышает время, необходимое для запуска одного шифрования. Причина этого в том, что по умолчанию узел имеет пул потоков из 4 потоков, поэтому первые 4 криптографических вызова выполнялись параллельно, а пятый вызов выполнялся после этого. Вы можете изменить количество потоков вручную, добавив
process.env.UV_THREADPOOL_SIZE = 2
в начале файла. Однако в Windows это не сработает, и вам нужно будет установить переменную среды перед запуском приложения узла, как показано ниже:
установите UV_THREADPOOL_SIZE=2 и узел app.js
Итак, вкратце, вот как реализован цикл событий node js. Есть некоторые другие исключительные особенности того, как Node обрабатывает вызовы set-immediate и события закрытия в цикле обработки событий, однако они могут немного сбивать с толку и не очень полезны для понимания ключевых концепций Node.
Согласно названию статьи, ниже приводится сводка различий двух реализаций цикла обработки событий.
- Веб-API предоставляют асинхронные события, такие как события DOM, тайм-ауты, вызовы API для среды выполнения JS, а в узле библиотека Libuv в основном обрабатывает их.
- Node использует пул потоков с 4 потоками по умолчанию для эффективной обработки задач ввода-вывода, сети и т. д.
- В JS нет порядка обработки событий. Однако Node рассматривает процессы по порядку (тайм-аут, процессы ОС и т. д.).
- Программа не завершается, когда в цикле событий JS нет ожидающих выполнения задач, однако Node завершает работу программы, когда в цикле обработки событий нет ожидающих выполнения задач.