Javascript
Является ли NodeJs однопоточным?
Как работать с другими потоками в Nodejs?
Node.js запускает код JavaScript в одном потоке, что означает, что ваш код может выполнять только одну задачу за раз. Но сам Node.js является многопоточным и предоставляет скрытые потоки через библиотеку `libuv`, которая обрабатывает операции ввода-вывода, такие как чтение файлов с диска или сетевые запросы. Благодаря использованию скрытых потоков Node.js предоставляет асинхронные методы, которые позволяют вашему коду выполнять запросы ввода-вывода, не блокируя основной поток.
Node.js представил нам модуль рабочих потоков. Этот модуль позволяет создавать потоки и выполнять задачи Javascript параллельно. Если поток завершает задачу, он отправляет сообщение в основной поток, содержащее результат, чтобы мы могли использовать его в основном потоке. Рабочие потоки не блокируют основной поток, поэтому мы можем разделить и распределить нашу задачу на несколько частей, чтобы оптимизировать ее.
Давайте сделаем это на примере и посмотрим на него поближе.
Создаем проект Nodejs и устанавливаем Express framework
mkdir multiple-thread cd multiple-thread npm init -y npm install express
Краткая информация о экспресс-версии Express используется для создания серверного приложения с блокирующими и неблокирующими конечными точками.
Прежде всего, это процессы и потоки. Мы должны знать различия между ними.
Что такое процесс?
Процесс запускает программу в ОС. Он может выполнять одну задачу за раз.
Давайте рассмотрим процесс поближе. Создайте файл JS и зарегистрируйте процесс.
//ex-process.js const processName = process.argv.slice(2)[0] let count = 0; while(true){ count++; if(count === 3333 || count=== 6666){ console.log(`${processName}: ${count}`) } }
В этой задаче она никогда не закончится. Таким образом, мы можем легко смотреть на процесс. Запустите задачу;
node ex-process.js FirstEx & // & symbol means the Node program to run in the background, so we can use our teminal for other works Output [1] 82942 FirstEx: 3333 FirstEx: 6666
С помощью `ps` мы можем увидеть идентификатор процесса всех запущенных процессов (82942). Если мы хотим, чтобы только процессы Node
ps |grep node Output 82942 ttys001 2:38.85 node ex-process.js FirstEx 83105 ttys001 0:00.00 grep node
Что такое нити?
Потоки — это относительно легкий способ реализации нескольких путей выполнения внутри приложения. На системном уровне программы выполняются параллельно, при этом система распределяет время выполнения каждой программы в зависимости от ее потребностей и потребностей других программ.
Давайте закодируем Express Server с неблокирующим маршрутом и блокирующими маршрутами.
Давайте напишем обычный блокирующий маршрут для пользователей. В 1_ ;
const express = require('express'); const app = express(); app.get('/hello', (req,res)=>{ res.status(200).send(`Hello`); }) app.get('/blocking-route', (req,res)=>{ console.log('Blocking route called') let counter = 0; for (let i = 0; i < 15_000_000_000; i++) { counter++; } res.status(200).send(`result is ${counter}`); }) app.listen(7333, ()=> console.log('Server started on port 7333'));
Запустим сервер с узлом app.js. blocking-route
содержит задачу, интенсивно использующую ЦП. Эта задача выполняется на ЦП и займет много времени. Теперь зайдите http://localhost:7333/hello
в браузер. Мы увидим мгновенный ответ. Он содержит наше сообщение. Для тестирования блокировки откройте новую вкладку и перейдите http://localhost:7333/blockint-route
. Когда мы дождемся нашего ответа, откройте новую вкладку и перейдите http://localhost:7333/hello
. Мы не получим мгновенный ответ, как раньше, и он попытается загрузить страницу.
Он не отвечает, пока blocking-route
не завершится и не отправит ответ.
Почему он не ответил нам мгновенно?
В этом примере цикл for с привязкой к процессору блокирует основной поток. Пока основной поток заблокирован, Node.js не может обрабатывать какие-либо запросы, пока не завершится процесс, привязанный к ЦП. поэтому ни один из других маршрутов, таких как /hello
, не работает при загрузке маршрута /blocking-route
.
Таким образом, если у нас есть API с тысячами одновременных GET
запросов к неблокирующему маршруту, такому как /hello
, только один запрос к /blocking-route
заблокирует все маршруты в API. Это означает, что все маршруты приложений не отвечают.
JavaScript DAAA IT, мы не можем использовать другой поток
Dudeeeee Это не только JS. Это Нодейс. Мы можем разгрузить задачу, связанную с процессором, с помощью рабочих потоков.
В первую очередь нужно проверить наши ядра командой nproc
nproc output 8
Если число ядер больше 2 или равно 2, мы можем продолжить.
Давайте создадим еще один JS-файл, например worker.js
, имя на ваше усмотрение. В этом файле мы выполняем задачу с интенсивным использованием процессора
//worker.js const { parentPort } = require("worker_threads"); let counter = 0; console.log("Worker started"); for (let i = 0; i < 15_000_000_000; i++) { counter++; } parentPort.postMessage(counter);
Мы вызываем метод postMessage(), который отправляет сообщение в основной поток. Сообщение содержит параметры, которые мы реализуем. Итак, мы должны вызвать его в нашем основном приложении, верно?
//app.js const express = require("express"); const { Worker } = require("worker_threads"); const app = express(); app.get("/blocking-route", async (req, res) => { console.log("Blocking route called"); let counter = 0; for (let i = 0; i < 15_000_000_000; i++) { counter++; } res.status(200).send(`result is ${counter}`); }); app.get("/hello", (req, res) => { res.status(200).send(`Hello`); }); app.get("/non-blocking-route", async (req, res) => { console.log("Non-blocking route called"); const worker = new Worker("./worker.js"); worker.on("message", (data) => { res.status(200).send(`result is ${data}`); }); worker.on("error", (err) => { res.status(404).send(` error ${err}`); }); console.log("I am not blocked"); }); app.listen(7333, () => console.log("Server started on port 7333"));
Мы добавили Worker
из модуля worker_threads и реализовали его. Мы прикрепили события к рабочему экземпляру для ошибок и сообщений. Если возникает ошибка, обратный вызов возвращает пользователю ответ 404
, содержащий сообщение об ошибке.
Давайте запустим сервер с node app.js
. Откройте браузер и перейдите на http://localhost:7333/non-blocking-route
, прежде чем он вернет ответ, перейдите на http://localhost:7333/hello
. Вы поняли это? Получаем ответ моментально. Блокировки нет. Мы не стали ждать, пока другие маршруты закончат загрузку. Потому что задача, привязанная к ЦП, разгружается и работает в другом потоке, а основной поток обрабатывает все запросы. Мы можем использовать больше работников для повышения нашей производительности. Например, у меня 8 ядер и я могу разделить свой Процесс на 8.
Заключение
Рабочие процессы (потоки) могут помочь с операциями JavaScript, интенсивно использующими ЦП. В отличие от child_process
или cluster
, worker_threads
может совместно использовать память.
В этой статье я попытался упростить задачу и объяснить, как работает worker_threads?
Спасибо за прочтение