Процесс с привязкой к процессору блокирует рабочий пул при использовании дочернего процесса на HTTP-сервере NestJS
Версия узла: v10.13.0
Я пробую очень простой тест на параллелизм запросов NodeJS, включающий большой расчет CPU. Я понимаю, что NodeJS - не лучший инструмент для процессов, связанных с процессором, и что дочерний процесс не следует генерировать систематически, но этот код предназначен для тестирования того, как работает дочерний процесс. Также это написано в TypeScript, используя NestJS.
SRC/app.controller.ts
import { Get, Param, Controller } from '@nestjs/common';
import fork = require('child_process');
@Controller()
export class AppController {
@Get()
async root(): Promise<string> {
let promise = new Promise<string>(
(resolve, reject) => {
// spawn new child process
const process = fork.fork('./src/cpu-intensive.ts');
process.on('message', (message) => {
// when process finished, resolve
resolve( message.result);
});
process.send({});
}
);
return await promise;
}
}
SRC/CPU-intensive.ts
process.on('message', async (message) => {
// simulates a 10s-long process
let now = new Date().getTime();
let waittime = 10000; // 10 seconds
while (new Date().getTime() < now + waittime) { /* do nothing */ };
// send response to master process
process.send({ result: 'Process ended' });
});
Такой длительный процесс, если он выполняется без появления новых дочерних процессов, приводит к этой временной шкале результатов с 5 одновременными запросами (отмеченными с № 1 по № 5). Каждый процесс, блокирующий событие цикла, каждый запрос должен ждать, пока предыдущие будут завершены, чтобы ответить.
Time 0 10 20 30 40 50
#1 +----+
#2 +----+----+
#3 +----+----+----+
#4 +----+----+----+----+
#5 +----+----+----+----+----+
При появлении новых дочерних процессов я ожидал, что каждый процесс будет обрабатываться одновременно с помощью другого логического ядра моего процессора (у меня есть 8 логических ядер), что приводит к этой предсказанной временной шкале:
Time 0 10 20 30 40 50
#1 +----+
#2 +----+
#3 +----+
#4 +----+
#5 +----+
Хотя, я наблюдаю этот странный результат по каждому тесту:
Time 0 10 20 30 40 50
#1 +----+
#2 +----+----+
#3 +----+----+----+
#4 +----+----+----++
#5 +----+----+----+-+
Первые 3 запроса действуют так, как будто рабочий пул был голоден, хотя я бы предположил, что были созданы 3 разных пула. Два последних запроса очень сбивают с толку, поскольку они действуют как работающие одновременно с запросом №3.
В настоящее время я ищу объяснение:
- почему первые 3 запроса не действуют так, как если бы они выполнялись одновременно
- почему последние 3 запроса действуют так, как если бы они выполнялись одновременно
Обратите внимание, что если я добавлю еще один "быстрый" метод следующим образом:
@Get('fast')
async fast(): Promise<string> {
return 'Fast process ended.';
}
на этот метод не влияют процессы с интенсивным использованием ЦП, выполняемые в параллелизм, и ответы всегда мгновенно.
Ответы
Ответ 1
Я выполнил тестовую версию на своей машине и ее работоспособность, вы можете проверить это на своей машине.
Версия узла: v8.11.2 ОС: macOs High Sierra 10.13.4, 8 ядер
ребенок-процесс-test.js
const child_process = require('child_process');
for(let i=0; i<8; i++) {
console.log('Start Child Process:',i,(new Date()));
let worker_process = child_process.fork("cpu-intensive-child.js", [i]);
worker_process.on('close', function (code) {
console.log('End Child Process:', i , (new Date()), code);
});
}
Процессорные интенсивно-child.js
const fs = require('fs');
// simulates a 10s-long process
let now = new Date().getTime();
let waittime = 10000; // 10 seconds
while (new Date().getTime() < now + waittime) { /* do nothing */ };
// send response to master process
// process.send({ result: 'Process ended' });
Выход
Вы можете проверить выход, разница составляет всего 10 sec
для всего процесса, вы можете выполнить этот тестовый пример на своем компьютере и сообщить мне, может быть, это может помочь.