Многопроцессорность Node.js для ресурсоемких задач

Node.js

Однопоточный и многопроцессный Node.js

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

Конечно, решить эту проблему невозможно. Хотя Node.js не поддерживает многопоточность, для выполнения задач можно создать несколько дочерних процессов. Node.js предоставляетchild_processиclusterДва модуля можно использовать для создания процессов с несколькими дочерними элементами.

Далее мы будем использовать один поток и несколько процессов, чтобы имитировать поиск большого количества чисел Фибоначчи для тестирования с интенсивным использованием ЦП.

Следующий код предназначен для нахождения числа Фибоначчи с позицией 35 500 раз (удобно для тестирования, установите позицию, которая не должна быть слишком длинной или слишком короткой)

однопоточная обработка

Код:single.js

function fibonacci(n) {
  if (n == 0 || n == 1) {
    return n;
  } else {
    return fibonacci(n - 1) + fibonacci(n - 2);
  }
}

let startTime = Date.now();
let totalCount = 500;
let completedCount = 0;
let n = 35;

for (let i = 0; i < totalCount; i++) {
  fibonacci(n);
  completedCount++;
  console.log(`process: ${completedCount}/${totalCount}`);
}
console.log("👏 👏 👏 👏 👏 👏 👏 👏 👏 👏");
console.info(`任务完成,用时: ${Date.now() - startTime}ms`);
console.log("👏 👏 👏 👏 👏 👏 👏 👏 👏 👏");

воплощать в жизньnode single.jsПосмотреть Результаты

На моем компьютере результат отображается как44611ms(Будут различия в разных конфигурациях компьютеров).

...
process: 500/500
👏 👏 👏 👏 👏 👏 👏 👏 👏 👏
任务完成,用时: 44611ms
👏 👏 👏 👏 👏 👏 👏 👏 👏 👏

Нахождение 500 раз занимает 44 секунды, что слишком медленно. Можно предположить, что чем больше место, тем больше количество...

Тогда давайте попробуем использовать мультипроцесс ⬇️

мультипрогресс

использоватьclusterмодуль,Master-Workerрежим для проверки Всего 3 js, соответственно код основного потока:master.js, код дочернего процесса:worker.js, код входа:cluster.js(Запись не нужно писать в отдельный js, вот чтобы было понятнее)

Код основного потока:master.js

const cluster = require("cluster");
const numCPUs = require("os").cpus().length;

// 设置子进程执行程序
cluster.setupMaster({
  exec: "./worker.js",
  slient: true
});

function run() {
  // 记录开始时间
  const startTime = Date.now();
  // 总数
  const totalCount = 500;
  // 当前已处理任务数
  let completedCount = 0;
  // 任务生成器
  const fbGenerator = FbGenerator(totalCount);

  if (cluster.isMaster) {
    cluster.on("fork", function(worker) {
      console.log(`[master] : fork worker ${worker.id}`);
    });
    cluster.on("exit", function(worker, code, signal) {
      console.log(`[master] : worker ${worker.id} died`);
    });

    for (let i = 0; i < numCPUs; i++) {
      const worker = cluster.fork();

      // 接收子进程数据
      worker.on("message", function(msg) {
        // 完成一个,记录并打印进度
        completedCount++;
        console.log(`process: ${completedCount}/${totalCount}`);

        nextTask(this);
      });

      nextTask(worker);
    }
  } else {
    process.on("message", function(msg) {
      console.log(msg);
    });
  }

  /**
   * 继续下一个任务
   *
   * @param {ChildProcess} worker 子进程对象,将在此进程上执行本次任务
   */
  function nextTask(worker) {
    // 获取下一个参数
    const data = fbGenerator.next();
    // 判断是否已经完成,如果完成则调用完成函数,结束程序
    if (data.done) {
      done();
      return;
    }
    // 否则继续任务
    // 向子进程发送数据
    worker.send(data.value);
  }

  /**
   * 完成,当所有任务完成时调用该函数以结束程序
   */
  function done() {
    if (completedCount >= totalCount) {
      cluster.disconnect();
      console.log("👏 👏 👏 👏 👏 👏 👏 👏 👏 👏");
      console.info(`任务完成,用时: ${Date.now() - startTime}ms`);
      console.log("👏 👏 👏 👏 👏 👏 👏 👏 👏 👏");
    }
  }
}

/**
 * 生成器
 */
function* FbGenerator(count) {
  var n = 35;
  for (var i = 0; i < count; i++) {
    yield n;
  }
  return;
}

module.exports = {
  run
};

1. Здесь дочерний процесс создается по количеству логических ядер процессора текущего компьютера.Количество разных компьютеров будет разным.У моего процессора 6 физических ядер.Так как он поддерживает гиперпоточность, логическое количество ядер равно 12, поэтому будет создано 12 дочерних процессов

2. Связь между основным потоком и дочерним процессом осуществляется черезsendспособ отправки данных, прослушиваниеmessageсобытие для получения данных

3. Я не знаю, заметили ли вы, что здесь я использовал Генератор ES6 для имитации и генерации положения числа Фибоначчи, которое необходимо искать каждый раз (хотя оно жестко закодировано 😂, чтобы обеспечить единство с одним потоком выше). Это для того, чтобы все задания не выкинулись за один раз, ведь даже если и выкинут, то они будут заблокированы.Лучше ставить их на программу и контролировать, одно выполнить, другое поставить.

Код подпроцесса:worker.js

function fibonacci(n) {
  if (n == 0 || n == 1) {
    return n;
  } else {
    return fibonacci(n - 1) + fibonacci(n - 2);
  }
}

// 接收主线程发送过来的任务,并开始查找斐波那契数
process.on("message", n => {
  var res = fibonacci(n);
  // 查找结束后通知主线程,以便主线程再度进行任务分配
  process.send(res);
});

Код входа:cluster.js

// 引入主线程js,并执行暴露出来的run方法
const master = require("./master");
master.run();

воплощать в жизньnode cluster.jsПосмотреть Результаты

На моем компьютере результат отображается как10724ms(Будут различия в разных конфигурациях компьютеров).

process: 500/500
👏 👏 👏 👏 👏 👏 👏 👏 👏 👏
任务完成,用时: 10724ms
👏 👏 👏 👏 👏 👏 👏 👏 👏 👏

результат

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

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

читать оригинал