关于NodeJS的子进程
1.什么是进程
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
进程的概念主要有两点:
- 进程是一个实体,每一个进程都有自己的地址空间。
- 进程是一个"执行中的程序",存在嵌套关系。
我们可以通过下面的命令来查看当前电脑启动了多个进程:
- 查看所有进程
tasklist
(window) - 查看某个进程
tasklist | findstr "进程名"
(window)
进程的嵌套关系大致如下:
- 首先是操作系统进程OS
- 其次是启动进程,所有的进程都是由sbin/launchd来启动的,这个其实就是一个桌面进程
- 我们打开了 webstorm/vscode 的编辑器,就相当于开启了一个webstorm/vscode的进程
- 在编辑器进程中启动了node应用,所以需要有个node进程
- 在node进程中利用child_process创建nodejs的子进程
2.为什么需要子进程
Node.js中的非阻塞单线程的特性对单进程任务是非常有用。但是事实上,面对日益复杂的业务逻辑,单个cpu中的单进程所能提供的计算力显然是不足的。因为无论服务器如何强大,单线程只可以利用有限的资源。
子进程是核心模块,允许用户创建和控制子进程。这些进程可以执行系统命令、运行各种语言的脚本,甚至可以创建新的 Node.js 实例。child 进程模块的主要目的是允许同时执行多个进程,而不会阻塞主事件循环。
对于需要处理 CPU 密集型操作或执行外部命令和脚本的应用来说,使用 child 进程可以确保应用的高性能和响应性。
3.子进程的使用方式
child 进程可以用于以下任务:
- 并行处理:Child 处理通过允许应用程序将工作负载分布在多个CPU核心上,从而显著提高 CPU 密集型活动(如图像处理和数据分析)的性能。
- 运行 shell 脚本:Child 进程可以用来执行 shell 脚本。您可以使用 exec 技术来运行 shell 命令并捕获它们的输出,还可以使用 spawn 方法,当直接运行脚本时提供更大的控制。
- 与其他服务通信:在通信方面,child 进程起着至关重要的作用。它可以与外部服务(如数据库、API 或微服务)进行通信。它们可以用来调用外部 API,执行数据库查询,与其它微服务通信。
4.子进程的创建方式
要创建一个 child 进程,Node.js 为我们提供了四种主要方法来创建一个 child 进程,分别是 exec(), execFile(), spawn(), fork()。
4.1 exec():执行shell脚本命令
exec() 方法在 shell 中运行一个命令并缓冲输出。它适用于运行 shell 命令并记录输出。但由于是缓冲,它有内存限制。
下面是一个使用 exec() 方法执行命令并获取和处理一些系统信息的例子。
const {
exec} = require('child_process');
const iconv= require('iconv-lite');//需要用这个转码,不然会乱码
// 执行命令时将 encoding 设为 'buffer'
exec('dir', {
encoding: 'buffer' }, (error, stdout, stderr) => {
if (error) {
console.error(`执行命令出错: ${
error}`);
return;
}
// 假定 Windows 系统使用 CP936 编码(常见于中文系统)
const output = iconv.decode(stdout, 'cp936');
console.log('命令输出:', output);
});
4.2 execFile():执行shell脚本文件
execFile 用来执行一个shell脚本文件。
// shell脚本: test.shell
ls -al | grep package.json
echo $1
const {
execFile } = require('child_process');
const path = require('path');
execFile(path.resolve(__dirname, 'test.shell'), [], (error, stdout, stderr) => {
if (error) {
console.error(`execFile error: ${
error.message}`);
return;
}
if (stderr) {
console.error(`stderr: ${
stderr}`);
return;
}
console.log(`stdout: ${
stdout}`);
});
execFile() 方法在没有 shell 的情况下运行可执行文件。它比 exec() 更高效,因为它避免了 shell 的开销。
不过execFile也能执行命令,不过配置必须放在第二个参数中,第一个参数只能是命令。
const {
execFile } = require('child_process');
execFile("node", ["--version"], (error, stdout, stderr) => {
if (error) {
console.error(`execFile error: ${
error.message}`);
return;
}
if (stderr) {
console.error(`stderr: ${
stderr}`);
return;
}
console.log(`stdout: ${
stdout}`);
});
4.3 spawn() 方法
与主要用于执行 shell 命令或可执行文件并捕获其输出的 exec 方法不同,spawn() 方法通过基于流的输出处理、直接与可执行文件交互、事件驱动的通信,提供了对 child 进程的更多控制。
spawn() 方法可用于需要直接交互的复杂场景。spawn() 启动一个新进程,给定一个命令,并提供流 i.e stdout, stderr 用于直接处理进程的输出和错误。
下面是一个如何使用 spawn() 方法的例子
const {
spawn } = require("child_process");
const npm = process.platform === "win32" ? "npm.cmd" : "npm";
const npmCommand = spawn(npm, ["-v"]);
npmCommand.stdout.on("data", (data) => {
console.log(`stdout: ${
data}`);
});
npmCommand.stderr.on("data", (data) => {
console.error(`stderr: ${
data}`);
});
npmCommand.on("close", (code) => {
console.log(`child process exited with code ${
code}`);
});
4.4 fork() 方法
在javascript中,在处理大量计算的任务方面,HTML里面通过web work来实现,使得任务脱离了主线程。在node中使用了一种内置于父进程和子进程之间的通信来处理该问题,降低了大数据运行的压力。node中提供了fork方法,通过fork方法在单独的进程中执行node程序,并且通过父子间的通信,子进程接受父进程的信息,并将执行后的结果返回给父进程。
- 在子进程中:通过process.on(‘message’)和process.send()的机制来接收和发送消息。
- 在父进程中:通过child.on(‘message’)和process.send()的机制来接收和发送消息。
适用场景:并行执行 JavaScript 文件,父子进程间通过消息通信。
//index.js
const {
fork } = require('child_process');
const path = require('path');
const child = fork(path.resolve(__dirname, 'child.js'));
child.on('message', message => {
console.log(`来自子进程的消息: ${
message}`);
});
child.send("child");
setInterval(() => {
child.send("child");
}, 1000);
//child.js
process.on('message',function(msg){
console.log("来自父进程的消息:", msg);
})
process.send("parent");
setInterval(() => {
process.send("parent");
}, 1000);
使用 fork() 时,每个子进程都是一个独立的 Node.js 实例,具有独立的 V8 实例和事件循环。这意味着创建大量子进程可能会导致大量的资源消耗。
4.5 总结
选择合适的子进程创建方法取决于你的需求。如果你只需执行简单的 shell 命令,使用exec。对于可执行文件,使用execFile。如果需要并行执行 JavaScript 文件且支持消息通信,使用fork。而spawn适用于执行复杂命令并进行流式处理。根据不同的情况,选择最合适的方法可以提高代码的效率和可读性。
方法 | 区别 | 适用场景 |
---|---|---|
exec | 执行 shell 命令,创建 shell进程 | 执行简单命令,获取输出和错误信息 |
execFile | 执行可执行文件 | 执行编译好的二进制文件,获取输出和错误信息 |
fork | 衍生 Node.js 子进程,执行 JS 文件 | 创建独立的子进程并与父进程通过消息通信 |
spawn | 执行命令,不创建 shell 进程 | 执行复杂命令,流式处理输入输出 |