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?

Спасибо за прочтение

›REF