一个nodejs
程序执行是从上到下每一行执行一次( 单线程 )的。现在,如果线程中有一个同步块,或者有像使用加密加密/解密那样的cpu
密集操作,它将阻止进一步的执行。需要卸载类似这些任务,以保持主线程的自由。这可以通过子进程来实现,这正是这篇博客将介绍的。
process
不一定是特定的概念。这是我们在系统上运行程序的一个实例。打开浏览器或打开这个程序都是调用一个process
。
同样,在nodejs
中创建的应用程序在执行时也会有一个process
。我们可以使用process
,nodejs
已经内置了process
模块,不必通过 require
引入。
因此,当一个过程被执行,并且有一个需要花费一些时间才能运行的任务时,最好将任务从主线程移开,以避免任何阻塞。nodejs
中的child process
模块可以让我们将这些繁重的任务放到子流程中,而子流程又可以使得我们的主线程保持自由。
在child process
模块中,基本上有3个功能可以帮助我们创建子流程:
Spawn
Exec & ExecFile
Fork
spawn
方法会在 process
中运行一个命令。在运行该命令后,数据将以流的形式返回。这意味着它是异步的,所以它不会阻塞事件循环。每当一段数据准备作为响应被送回时,它就会被传递到标准输出流,如果在运行命令时出现错误,它将被传递到标准错误流:
const { spawn } = require('child_process');
let listFiles = spawn("dir", { shell : true })
listFiles.stderr.on("data", (error) => {
console.log(error.toString())
})
listFiles.stdout.on("data", (data) => {
console.log(data.toString())
})
listFiles.on('error', (error) => {
console.error(`Some error occurred: ${error.message}`);
});
现在 dir
命令列出当前工作目录中的文件和文件夹。类似于 Linux
中的 ls
。对于 Windows
系统,在运行某些脚本/命令之前可能需要一个shell
。这就是为什么要在命令之后设置一个shell
选项的原因。如果你正在使用一个Linux
系统,就不需要设置它。
因此,一旦将命令存储在变量中,我们就可以通过使用相应的流来监听任何响应或错误。
标准错误(stderr
):如果我们试图执行的命令中有问题,会返回一个错误。
标准输出( stdout
):以流的形式返回数据。
两个流都返回一个缓冲字符串,所以需要使用了 toString()
方法把它转换成可读的格式。
exec
与 spawn
有些相似。同样在一个process
运行一个命令,但是响应不会以流的形式出现。它储存在缓冲器(buffer
) 之后作为一个整体送回去。因此响应数据的大小没有限制,因为它是以块的形式返回的,在一段时间内可能会得到一个巨大的响应。在使用 exc
的情况下,它等待先填充缓冲区。一旦它被填充,它将返回整个响应。
但是如果响应的大小大于缓冲区呢?如果是这样的话,我们的应用程序就会崩溃,所以如果我们事先知道你的响应数据将是非常巨大的情况,应该选择spawn
方法,而不是 exec
。
const { exec } = require('child_process')
exec("copy script.js newFile.js", (error, stdout, stderr) => {
if (error) { return console.log(error) }
if (stderr) { return console.log(stderr) }
console.log(stdout);
});
我们不需要在 exec
的情况下配置 shell
选项,因为它在默认情况下运行一个shell
命令。这次我们有一个回调函数,而不是通过监听来处理流。
在上面的例子中,运行一个 copy
命令,它将把第一个文件(script.js
)的内容复制到第二个文件( newFile.js
)中。一旦内容被复制,响应数据将被存储在一个缓冲区中,并且只有当获得整个数据时,我们才能获得响应。
此外,在使用 exec
时,我们可以直接在命令本身中传递参数。如果是spawn
我们就不能这么做,必须传递数组中的参数。例如:
// 传递整个参数
exec("copy script.js newFile.js", callback);
// 通过参数数组形式参数
spawn(“copy”, [“script.js”, “xyz.js”])
execFile
是用来在单独的process
中运行可执行文件的,不同于运行exec
命令。
execFile("node", ["--version"], (error, stdout, stderr) => {
if(error) { throw error }
console.log("Output: " + stdout)
})
上面的代码将返回我们的系统上Nodejs
的版本。跟spawn
一样需要在数组中传递参数,。由于在示例的环境路径变量中有node
可执行,就不必传递整个文件位置。
execFile("C:/Program Files/nodejs/node", ["--version"], (error, stdout, stderr) =>{
if(error) { throw error }
console.log("Output: " + stdout)
})
就Linux
系统而言,通常情况下 bin
和 sbin
目录将有可执行文件。Windows
系统通常将默认可执行文件存储在程序文件中,还可以在execFile
中运行shell
脚本。
execFile
不像exec
不会在shell
中运行命令。相反,它将可执行文件生成为一个新的子进程,使其比exec
稍微有效一些。如果在Windows
系统上运行文件有问题,可以跟spawn
一样配置一个shell
选项。
我们将研究的最后一个方法是fork
。这个方法和前面方法都一样,产生了一个nodejs
进程,但这次它也调用了一个模块,该模块在子和父进程之间创建一个IPC
通道。IPC
代表流程间沟通,它基本上是一套操作系统提供的机制,允许流程管理共享数据。我们不需要深入了解IPC
,只需要知道的是,它将在子进程和父进程之间打开一个通道,以便它们能够在某个事件中相互发送消息。
此方法将以文件的URL
作为其第一个参数。因此子进程逻辑现在将存在于此文件中。
// scirpt.js - Parent
const child = fork("./child.js");
让我们也创建子进程文件。在这个文件中,我们只有简单的一行打印:
// child.js - Child
console.log("Hello world")
现在,如果运行script.js
文件,马上就会得到控制台日志消息。因此,这基本上意味着,当我们在父进程中创建一个fork
时,子进程文件中的代码就执行了。
还可以向此方法传递第二个参数,该参数是一个字符串数组,作为子过程的参数。在子进程中,还可以使用 argv
这样的属性。
//script.js - Parent
const child = fork("./child.js", ["hello", 1, "leo"]);
//child.js - Child
console.log(process.argv)
第三个参数是一组选项,可以传递到fork
进程中。对于这个例子,我们不需要担心这些选项,所以我们现在就跳过它,但是你可以稍后再读更多。只有第一个参数是强制性的,其余的是可选的。
现在让我们试着向这个fork
进程传递一个信息。我们可以用 send
方法:
//script.js - Parent
const child = fork("./child.js");
child.send({ message: "Hello child" })
子过程还需要知道父进程是否有任何新的消息。由于所有子进程都实现了事件emit API
。因此它们可以监听emit
事件。这也是我们能够从子进程发送事件的原因。
//child.js - Child
process.on("message", (msg) => {
console.log("Message from parent", msg);
})
运行此代码时,子进程接收了消息,但该进程尚未结束。它还在监听任何意味着它还在运行的事件。可以在带有exit
的流程上使用exit
方法手动退出。
//child.js - Child
process.on("message", (msg) => {
console.log("Message from parent", msg);
process.exit(0)
})
此时如果想在子进程上发送消息给子进程,同样需要监听事件。
//child.js - Child
process.send({ message: "Hello parent" })
process.on("message", (msg) => {
console.log("Message from parent", msg);
process.exit(0)
})
//script.js - Parent
const child = fork("./child.js");
child.send({ message: "Hello child" })
child.on("message", (msg) => {
console.log("Message from child", msg);
})
这篇简短的文章应该能让大家对如何在nodejs
中创建子进程任务以及如何在项目中实现它们有一个基本的了解。