Node.js入门之process模块和child_process模块
简介
本文主要介绍node
两个跟进程相关的process
模块和child_process
模块。process
是node
的全局模块,作用比较直观。可以通过它来获得node
进程相关的信息,child_process
主要用来创建子进程,可以有效解决node单线程效率不高的问题。
process
下面我们来看看process
的一些常用的属性和方法。
process.env
process.env
为node
运行服务的环境变量。里面默认的变量很多,笔者就不一一列举了。
比如我们常用的NODE_ENV
,我们执行NODE_ENV=production node process.js
console.log(process.env.NODE_ENV); // production
我们还可以传别的参数,比如我们执行aaa=dev node process.js
console.log(process.env.aaa); // dev
可以发现,通过key=value
这种方式传递的参数就是环境变量。
process.argv
process.argv
用来获取命令行参数,会返回一个数组。它的第一第二个参数是固定的,分别是node
可执行程序绝对路径和当前执行文件的绝对路径。后面的参数就是我们自己传传递的了。
比如我们执行node process.js name randy
console.log(process.argv);
输出如下
注意,这种参数是没有=
的。
process.execArgv
process.execArgv
用来获取特殊的参数。也就是运行node
程序特有的参数啦,比如 --harmony
。
我们来测试一下,执行node --harmony process.js name randy
console.log(process.execArgv); // [ '--harmony' ]
process.cwd()
process.cwd()
返回当前工作路径
console.log(process.cwd()); // /Users/randy/myproject/base-learn/node
process.chdir(directory)
process.chdir(directory)
切换当前工作路径
process.chdir("../npm");
console.log(process.cwd()); // /Users/randy/myproject/base-learn/npm
process.config
process.config
返回node
的编译配置相关参数。这个配置的参数也有很多,笔者就不一一列举了。
console.log("process.config", process.config);
process.pid
返回当前进程id。
console.log(process.pid); // 19540
process.title
返回和设置当前进程的名称,当你用ps
命令,同时有多个node
进程在跑的时候,作用就出来了。
process.title = "主进程";
console.log(process.title); // 主进程
process.uptime()
返回当前node进程已经运行了多长时间(单位是秒)。
console.log(process.uptime()); // 0.0291715
process.memoryUsage()
返回进程占用的内存,单位为字节。
console.log(process.memoryUsage());
返回如下
process.version
返回当前node的版本
console.log(process.version); // v14.21.1
process.versions
返回node
的版本,以及依赖库的版本
console.log(process.versions);
返回如下
process.execPath
返回 node
可执行程序的绝对路径
console.log(process.execPath); // C:\Users\lq\AppData\Roaming\nvm\v14.21.1\node.exe
process.arch
返回当前系统的处理器架构(字符串),比如'arm', 'ia32', or 'x64'。
console.log(process.arch); // x64
process.platform
返回当前系统平台描述的字符串
console.log(process.platform); // win32
process.nextTick(fn)
这个方法类似js
里面的promise.then()
,是node
中的微任务。不过还是有细微差别,后面讲node
事件循环的时候我们再细说。现在简单理解成微任务就行。
setTimeout(() => {
console.log("setTimeout");
}, 0);
process.nextTick(() => {
console.log("process.nextTick");
});
输出如下
我们可以看到,就算定时器在前,并且延迟是0毫秒也会在process.nextTick
后输出。
process.stdin、process.stdout、process.stderr
process.stdin、process.stdout、process.stderr
分别代表进程的标准输入、标准输出、标准错误输出。
process.stdin.setEncoding("utf8");
// 监听读取
process.stdin.on("readable", () => {
var chunk = process.stdin.read();
if (chunk !== null) {
process.stdout.write(`data: ${chunk}`);
}
});
// 读关闭
process.stdin.on("end", () => {
process.stdout.write("stdin end");
});
执行程序,可以看到,程序通过 process.stdin
读取用户输入的同时,通过 process.stdout
将内容输出到控制台
hello
data: hello
world
data: world
child_process
我们知道,node
是单线程的,当有密集计算时就会出现性能瓶颈,child_process
就是该性能瓶颈的一个解决方法。他能创建多个子进程分别处理,可以有效的提高程序的执行效率。
利用node
提供的child_process
模块,可以很容易的衍生出一个子进程,而且子进程之间可以通过事件消息系统进行互相通信。
node
的child_process
模块提供四种异步函数和三种同步函数的方式创建子进程:
spawn(command,[args],[options])
spawn
启动一个子进程,并执行命令。
该方法参数如下:
-
command, 需要运行的命令
-
args, 运行命令的参数, 是一个字符串数组
-
options, 配置项
- shell
<boolean>|<string>
如果是true
,则在 shell 内运行command
- stdio
<Array>|<string>
子进程的标准输入输出配置 - cwd
<string>
子进程的当前工作目录 - env
<object>
环境变量键值对,默认值:process.env
- shell
-
返回值 ChildProcess, 返回 ChildProcess 的实例
下面我们举个简单的例子,创建一个子进程并执行ls -al
命令。
const childProcess = require("child_process");
const ls = childProcess.spawn("ls", ["-al"]);
ls.stdout.on("data", function (data) {
console.log("data from child: " + data);
});
// 错误
ls.stderr.on("data", function (data) {
console.log("error from child: " + data);
});
ls.on("close", function (code) {
console.log("child exists with code: " + code);
});
输出如下
exec(command[, options][, callback])
exec
方法将会生成一个子shell
,然后在该 shell
中执行命令,并缓冲产生的数据,当子流程完成后,并将子进程的输出以回调函数参数的形式一次性返回。exec
方法会从子进程中返回一个完整的buffer
。
默认情况下,这个buffer
的大小应该是200k。如果子进程返回的数据大小超过了200k,程序将会崩溃,同时显示错误信息“Error:maxBuffer exceeded”。你可以通过在exec的可选项中设置一个更大的buffer体积来解决这个问题,但是你不应该这样做,因为exec本来就不是用来返回很多数据的方法。
该方法参数如下:
-
command, 需要运行的命令
-
options
cwd
:当前工作路径。env
:环境变量。encoding
:编码,默认是utf8
。shell
:用来执行命令的shell,unix上默认是/bin/sh
,windows上默认是cmd.exe
。timeout
:默认是0。killSignal
:默认是SIGTERM
。uid
:执行进程的uid。gid
:执行进程的gid。maxBuffer
: 标准输出、错误输出最大允许的数据量(单位为字节),如果超出的话,子进程就会被杀死。默认是200*1024(就是200k啦)
-
callback 回调函数,
下面我们举个简单的例子,创建一个子进程并执行ls -al
命令。
const childProcess = require("child_process");
childProcess.exec("ls -al", {encoding: 'utf8'}, function (error, stdout, stderr) {
if (error) {
console.error("error: " + error);
return;
}
console.log("stdout: " + stdout);
});
输出如下
execFile(file[, args][, options][, callback])
child_process.execFile()
函数与 child_process.exec()
类似,不同之处在于它默认不衍生 shell。 而是指定的可执行文件 file
直接作为新进程衍生,使其比 child_process.exec()
略有效率。
支持与 child_process.exec()
相同的options
。 由于未衍生 shell,因此不支持 I/O 重定向和文件通配等行为。
该方法参数如下:
-
file, 可以是执行文件的名字,或者路径。
-
args, 运行命令的参数, 是一个字符串数组
-
options
cwd
:当前工作路径。env
:环境变量。encoding
:编码,默认是utf8
。shell
:用来执行命令的shell,unix上默认是/bin/sh
,windows上默认是cmd.exe
。timeout
:默认是0。killSignal
:默认是SIGTERM
。uid
:执行进程的uid。gid
:执行进程的gid。maxBuffer
: 标准输出、错误输出最大允许的数据量(单位为字节),如果超出的话,子进程就会被杀死。默认是200*1024(就是200k啦)
-
callback 回调函数
下面我们举个简单的例子,创建一个子进程并执行node --version
命令。
const childProcess = require("child_process");
childProcess.execFile("node", ["--version"], function (error, stdout, stderr) {
if (error) {
throw error;
}
console.log("execFile", stdout); // execFile v14.21.1
});
fork(modulePath[, args][, options])
child_process.fork
是 spawn()
的特殊形式,用于在子进程中运行的模块,如 fork('./connectedChild.js')
相当于 spawn(‘node’, ['./connectedChild.js'])
。与spawn
方法不同的是,fork
会在父进程与子进程之间,建立一个通信管道,用于进程之间的通信。
该方法参数如下:
-
modulePath, 需要在子进程中运行的模块地址
-
args, 字符串参数列表
-
options 配置项
execPath
: 用来创建子进程的可执行文件,默认是/usr/local/bin/node
。也就是说,你可通过execPath
来指定具体的node可执行文件路径。(比如多个node版本)execArgv
: 传给可执行文件的字符串参数列表。默认是process.execArgv
,跟父进程保持一致。silent
: 默认是false
,即子进程的stdio
从父进程继承。如果是true
,则直接pipe
向子进程的child.stdin
、child.stdout
等。stdio
: 如果声明了stdio
,则会覆盖silent
选项的设置。
下面我们举个简单的例子,创建一个子进程,并完成父子进程的通信。
// connectedChild.js
// 监听主进程传递来的数据
process.on("message", (msg) => {
console.log("Message from parent:", msg);
// 如果当前进程是子进程,且与父进程之间通过IPC通道连接着,则process.connected为true
console.log("process.connected: " + process.connected);
});
// 发送数据给主进程
setTimeout(() => {
process.send({ name: "child message" });
// 断开与父进程之间的IPC通道,此时会将 process.connected 置为false
process.disconnect();
console.log("process.connected: " + process.connected);
}, 1000);
主进程通过fork
创建一个子进程执行connectedChild.js
。
const childProcess = require("child_process");
const forked = childProcess.fork("./connectedChild.js");
// 发送数据给子进程
forked.send({ hello: "world" });
// 监听子进程发送来的数据
forked.on("message", (msg) => {
console.log("Message from child", msg);
});
输出如下
总结
-
fork 和 spawn 方法返回的是一个stream
-
exec 和 execFile 方法会把执行结果放在callback中
-
execFile 会执行一个文件,跟exec不一样的地方在于,他不创建一个shell。
4 fork 是 spawn 的一种变体,在创建子进程的时候,进程之间会建立IPC通信channel,并通过 on('message', callbak), send(jsonobject) 来交换数据。
- fork、exec、execFile都是基于spawn的封装。
系列文章
后记
感谢小伙伴们的耐心观看,本文为笔者个人学习笔记,如有谬误,还请告知,万分感谢!如果本文对你有所帮助,还请点个关注点个赞~,您的支持是笔者不断更新的动力!
转载自:https://juejin.cn/post/7202809170378522680