查看 node.js 进程
The author selected the COVID-19 Relief Fund to receive a donation as part of the Write for DOnations program.
作者选择了COVID-19救济基金来接受捐赠,这是Write for DOnations计划的一部分。
When a user executes a single Node.js program, it runs as a single operating system (OS) process that represents the instance of the program running. Within that process, Node.js executes programs on a single thread. As mentioned earlier in this series with the How To Write Asynchronous Code in Node.js tutorial, because only one thread can run on one process, operations that take a long time to execute in JavaScript can block the Node.js thread and delay the execution of other code. A key strategy to work around this problem is to launch a child process, or a process created by another process, when faced with long-running tasks. When a new process is launched, the operating system can employ multiprocessing techniques to ensure that the main Node.js process and the additional child process run concurrently, or at the same time.
当用户执行单个Node.js程序时,它将作为单个操作系统(OS) 进程运行,该进程代表正在运行的程序实例。 在该过程中,Node.js在单个线程上执行程序。 如本系列前面的“ 如何在Node.js中编写异步代码”教程中所述,由于只有一个线程可以在一个进程上运行,因此花费很长时间在JavaScript中执行的操作会阻塞Node.js线程并延迟执行其他代码。 解决此问题的关键策略是在遇到长期运行的任务时启动子进程或由另一个进程创建的进程。 启动新进程时,操作系统可以采用多种处理技术来确保主Node.js进程和其他子进程同时运行或同时运行。
Node.js includes the child_process
module, which has functions to create new processes. Aside from dealing with long-running tasks, this module can also interface with the OS and run shell commands. System administrators can use Node.js to run shell commands to structure and maintain their operations as a Node.js module instead of shell scripts.
Node.js包含child_process
模块 ,该模块具有创建新进程的功能。 除了处理长时间运行的任务外,该模块还可以与OS交互并运行Shell命令。 系统管理员可以使用Node.js运行Shell命令来构建和维护其操作,而不是使用Shell脚本作为Node.js模块 。
In this tutorial, you will create child processes while executing a series of sample Node.js applications. You’ll create processes with the child_process
module by retrieving the results of a child process via a buffer or string with the exec()
function, and then from a data stream with the spawn()
function. You’ll finish by using fork()
to create a child process of another Node.js program that you can communicate with as it’s running. To illustrate these concepts, you will write a program to list the contents of a directory, a program to find files, and a web server with multiple endpoints.
在本教程中,您将在执行一系列示例Node.js应用程序时创建子进程。 您将使用child_process
模块创建进程,方法是使用exec()
函数通过缓冲区或字符串检索子进程的结果,然后使用spawn()
函数从数据流中检索子进程的结果。 您将使用fork()
创建另一个Node.js程序的子进程,以便在运行时与之通信。 为了说明这些概念,您将编写一个用于列出目录内容的程序,一个用于查找文件的程序以及一个具有多个端点的Web服务器。
You must have Node.js installed to run through these examples. This tutorial uses version 10.22.0. To install this on macOS or Ubuntu 18.04, follow the steps in How To Install Node.js and Create a Local Development Environment on macOS or the Installing Using a PPA section of How To Install Node.js on Ubuntu 18.04.
您必须安装Node.js才能运行这些示例。 本教程使用版本10.22.0。 要将其安装在macOS或Ubuntu 18.04上,请遵循如何在macOS上安装Node.js并创建本地开发环境中的步骤,或如何在Ubuntu 18.04上安装Node.js的 使用PPA安装部分中的步骤 。
This article uses an example that creates a web server to explain how the fork()
function works. To get familiar with creating web servers, you can read our guide on How To Create a Web Server in Node.js with the HTTP Module.
本文使用一个创建Web服务器的示例来说明fork()
函数的工作方式。 要熟悉创建Web服务器的方法,可以阅读有关如何使用HTTP模块在Node.js中创建Web服务器的指南。
exec()
创建子进程 (Step 1 — Creating a Child Process with exec()
)Developers commonly create child processes to execute commands on their operating system when they need to manipulate the output of their Node.js programs with a shell, such as using shell piping or redirection. The exec()
function in Node.js creates a new shell process and executes a command in that shell. The output of the command is kept in a buffer in memory, which you can accept via a callback function passed into exec()
.
当开发人员需要使用外壳来操纵其Node.js程序的输出(例如使用外壳管道或重定向)时,开发人员通常会在其操作系统上创建子进程来执行命令。 Node.js中的exec()
函数创建一个新的Shell进程并在该Shell中执行命令。 命令的输出保存在内存中的缓冲区中,您可以通过传递给exec()
的回调函数来接受该缓冲区。
Let’s begin creating our first child processes in Node.js. First, we need to set up our coding environment to store the scripts we’ll create throughout this tutorial. In the terminal, create a folder called child-processes
:
让我们开始在Node.js中创建第一个子进程。 首先,我们需要设置我们的编码环境来存储我们将在本教程中创建的脚本。 在终端中,创建一个名为child-processes
的文件夹:
Enter that folder in the terminal with the cd
command:
使用cd
命令在终端中输入该文件夹:
Create a new file called listFiles.js
and open the file in a text editor. In this tutorial we will use nano, a terminal text editor:
创建一个名为listFiles.js
的新文件,然后在文本编辑器中打开该文件。 在本教程中,我们将使用nano终端文本编辑器:
We’ll be writing a Node.js module that uses the exec()
function to run the ls
command. The ls
command list the files and folders in a directory. This program takes the output from the ls
command and displays it to the user.
我们将编写一个使用exec()
函数运行ls
命令的Node.js模块。 ls
命令列出目录中的文件和文件夹。 该程序从ls
命令获取输出,并将其显示给用户。
In the text editor, add the following code:
在文本编辑器中,添加以下代码:
const { exec } = require('child_process');
exec('ls -lh', (error, stdout, stderr) => {
if (error) {
console.error(`error: ${error.message}`);
return;
}
if (stderr) {
console.error(`stderr: ${stderr}`);
return;
}
console.log(`stdout:\n${stdout}`);
});
We first import the exec()
command from the child_process
module using JavaScript destructuring. Once imported, we use the exec()
function. The first argument is the command we would like to run. In this case, it’s ls -lh
, which lists all the files and folders in the current directory in long format, with a total file size in human-readable units at the top of the output.
我们首先使用JavaScript destructuring从child_process
模块导入exec()
命令。 导入后,我们将使用exec()
函数。 第一个参数是我们要运行的命令。 在这种情况下,它是ls -lh
,它以长格式列出当前目录中的所有文件和文件夹,并在输出的顶部以人类可读的单位显示总文件大小。
The second argument is a callback function with three parameters: error
, stdout
, and stderr
. If the command failed to run, error
will capture the reason why it failed. This can happen if the shell cannot find the command you’re trying to execute. If the command is executed successfully, any data it writes to the standard output stream is captured in stdout
, and any data it writes to the standard error stream is captured in stderr
.
第二个参数是具有三个参数的回调函数: error
, stdout
和stderr
。 如果命令运行失败, error
将捕获失败原因。 如果Shell无法找到您要执行的命令,则会发生这种情况。 如果命令成功执行,则写入标准输出流的所有数据都将在stdout
捕获,并将写入标准错误流的任何数据均将在stderr
捕获。
Note: It’s important to keep the difference between error
and stderr
in mind. If the command itself fails to run, error
will capture the error. If the command runs but returns output to the error stream, stderr
will capture it. The most resilient Node.js programs will handle all possible outputs for a child process.
注意:重要的是要牢记error
和stderr
之间的区别。 如果命令本身无法运行,则error
将捕获该错误。 如果命令运行但将输出返回到错误流, stderr
将捕获它。 最具弹性的Node.js程序将处理子进程的所有可能输出。
In our callback function, we first check if we received an error. If we did, we display the error’s message
(a property of the Error
object) with console.error()
and end the function with return
. We then check if the command printed an error message and return
if so. If the command successfully executes, we log its output to the console with console.log()
.
在回调函数中,我们首先检查是否收到错误。 如果这样做,则使用console.error()
显示错误message
( Error
对象的属性),并以return
结束函数。 然后,我们检查命令是否打印了错误消息,如果是,则return
错误消息。 如果命令成功执行,我们将使用console.log()
将其输出记录到控制台 。
Let’s run this file to see it in action. First, save and exit nano
by pressing CTRL+X
.
让我们运行此文件以查看其运行情况。 首先,按CTRL+X
保存并退出nano
。
Back in your terminal, run your application with the node
command:
回到终端,使用node
命令运行您的应用程序:
Your terminal will display the following output:
您的终端将显示以下输出:
Output
stdout:
total 4.0K
-rw-rw-r-- 1 sammy sammy 280 Jul 27 16:35 listFiles.js
This lists the contents of the child-processes
directory in long format, along with the size of the contents at the top. Your results will have your own user and group in place of sammy
. This shows that the listFiles.js
program successfully ran the shell command ls -lh
.
它以长格式列出child-processes
目录的内容,并在顶部显示内容的大小。 您的结果将拥有您自己的用户和组来代替sammy
。 这表明listFiles.js
程序成功运行了shell命令ls -lh
。
Now let’s look at another way to execute concurrent processes. Node.js’s child_process
module can also run executable files with the execFile()
function. The key difference between the execFile()
and exec()
functions is that the first argument of execFile()
is now a path to an executable file instead of a command. The output of the executable file is stored in a buffer like exec()
, which we access via a callback function with error
, stdout
, and stderr
parameters.
现在让我们看看执行并发进程的另一种方法。 Node.js的child_process
模块也可以使用execFile()
函数运行可执行文件。 之间的主要区别execFile()
和exec()
函数是的第一个参数execFile()
现在是一个可执行文件,而不是一个命令的路径。 可执行文件的输出存储在像exec()
这样的缓冲区中,我们通过带有error
, stdout
和stderr
参数的回调函数访问该缓冲区。
Note: Scripts in Windows such as .bat
and .cmd
files cannot be run with execFile()
because the function does not create a shell when running the file. On Unix, Linux, and macOS, executable scripts do not always need a shell to run. However, a Windows machines needs a shell to execute scripts. To execute script files on Windows, use exec()
, since it creates a new shell. Alternatively, you can use spawn()
, which you’ll use later in this Step.
注意: Windows中的脚本(例如.bat
和.cmd
文件)无法使用execFile()
运行,因为该函数在运行文件时不会创建外壳。 在Unix,Linux和macOS上,可执行脚本并不总是需要shell才能运行。 但是,Windows计算机需要外壳程序来执行脚本。 要在Windows上执行脚本文件,请使用exec()
,因为它会创建一个新的shell。 另外,您可以使用spawn()
,稍后将在本步骤中使用。
However, note that you can execute .exe
files in Windows successfully using execFile()
. This limitation only applies to script files that require a shell to execute.
但是,请注意,您可以使用execFile()
在Windows中成功执行.exe
文件。 此限制仅适用于需要外壳程序执行的脚本文件。
Let’s begin by adding an executable script for execFile()
to run. We’ll write a bash script that downloads the Node.js logo from the Node.js website and Base64 encodes it to convert its data to a string of ASCII characters.
让我们开始添加一个可执行脚本,以使execFile()
运行。 我们将编写一个bash脚本,该脚本从Node.js网站下载Node.js徽标 , Base64对其进行编码以将其数据转换为ASCII字符字符串。
Create a new shell script file called processNodejsImage.sh
:
创建一个名为processNodejsImage.sh
的新外壳脚本文件:
Now write a script to download the image and base64 convert it:
现在编写一个脚本来下载图像并进行base64转换:
#!/bin/bash
curl -s https://nodejs.org/static/images/logos/nodejs-new-pantone-black.svg > nodejs-logo.svg
base64 nodejs-logo.svg
The first statement is a shebang statement. It’s used in Unix, Linux, and macOS when we want to specify a shell to execute our script. The second statement is a curl
command. The cURL utility, whose command is curl
, is a command-line tool that can transfer data to and from a server. We use cURL to download the Node.js logo from the website, and then we use redirection to save the downloaded data to a new file nodejs-logo.svg
. The last statement uses the base64
utility to encode the nodejs-logo.svg
file we downloaded with cURL. The script then outputs the encoded string to the console.
第一个声明是shebang声明 。 当我们想指定一个shell来执行脚本时,它在Unix,Linux和macOS中使用。 第二条语句是curl
命令。 cURL实用程序 (其命令为curl
)是一种命令行工具,可以与服务器之间进行数据传输。 我们使用cURL从网站上下载Node.js徽标,然后使用重定向将下载的数据保存到新文件nodejs-logo.svg
。 最后一条语句使用base64
实用程序对我们使用cURL下载的nodejs-logo.svg
文件进行编码。 然后,脚本将编码后的字符串输出到控制台。
Save and exit before continuing.
保存并退出,然后继续。
In order for our Node program to run the bash script, we have to make it executable. To do this, run the following:
为了使我们的Node程序运行bash脚本,我们必须使其可执行。 为此,请运行以下命令:
This will give your current user the permission to execute the file.
这将向您的当前用户授予执行文件的权限。
With our script in place, we can write a new Node.js module to execute it. This script will use execFile()
to run the script in a child process, catching any errors and displaying all output to console.
使用我们的脚本后,我们可以编写一个新的Node.js模块来执行它。 该脚本将使用execFile()
在子进程中运行该脚本,捕获任何错误并将所有输出显示到控制台。
In your terminal, make a new JavaScript file called getNodejsImage.js
:
在您的终端中,创建一个名为getNodejsImage.js
的新JavaScript文件:
Type the following code in the text editor:
在文本编辑器中键入以下代码:
const { execFile } = require('child_process');
execFile(__dirname + '/processNodejsImage.sh', (error, stdout, stderr) => {
if (error) {
console.error(`error: ${error.message}`);
return;
}
if (stderr) {
console.error(`stderr: ${stderr}`);
return;
}
console.log(`stdout:\n${stdout}`);
});
We use JavaScript destructuring to import the execFile()
function from the child_process
module. We then use that function, passing the file path as the first name. __dirname
contains the directory path of the module in which it is written. Node.js provides the __dirname
variable to a module when the module runs. By using __dirname
, our script will always find the processNodejsImage.sh
file across different operating systems, no matter where we run getNodejsImage.js
. Note that for our current project setup, getNodejsImage.js
and processNodejsImage.sh
must be in the same folder.
我们使用JavaScript解构从child_process
模块导入execFile()
函数。 然后,我们使用该函数,将文件路径作为名字传递。 __dirname
包含将其写入的模块的目录路径。 模块运行时,Node.js将__dirname
变量提供给模块。 通过使用__dirname
,无论我们在何处运行getNodejsImage.js
,我们的脚本都将始终跨不同的操作系统查找processNodejsImage.sh
文件。 请注意,对于我们当前的项目设置, getNodejsImage.js
和processNodejsImage.sh
必须位于同一文件夹中。
The second argument is a callback with the error
, stdout
, and stderr
parameters. Like with our previous example that used exec()
, we check for each possible output of the script file and log them to the console.
第二个参数是带有error
, stdout
和stderr
参数的回调。 与前面使用exec()
示例一样,我们检查脚本文件的每个可能输出,并将它们记录到控制台。
In your text editor, save this file and exit from the editor.
在文本编辑器中,保存此文件并退出编辑器。
In your terminal, use node
to execute the module:
在终端中,使用node
执行模块:
Running this script will produce output like this:
运行此脚本将产生如下输出:
Output
stdout:
PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNDQyLjQgMjcwLjkiPjxkZWZzPjxsaW5lYXJHcmFkaWVudCBpZD0iYiIgeDE9IjE4MC43IiB5MT0iODAuNyIge
...
Note that we truncated the output in this article because of its large size.
请注意,由于篇幅较大,我们将其截断。
Before base64 encoding the image, processNodejsImage.sh
first downloads it. You can also verify that you downloaded the image by inspecting the current directory.
在对图像进行base64编码之前, processNodejsImage.sh
首先下载它。 您还可以通过检查当前目录来验证是否下载了映像。
Execute listFiles.js
to find the updated list of files in our directory:
执行listFiles.js
以在我们的目录中找到文件的更新列表:
The script will display content similar to the following on the terminal:
该脚本将在终端上显示类似于以下内容的内容:
Output
stdout:
total 20K
-rw-rw-r-- 1 sammy sammy 316 Jul 27 17:56 getNodejsImage.js
-rw-rw-r-- 1 sammy sammy 280 Jul 27 16:35 listFiles.js
-rw-rw-r-- 1 sammy sammy 5.4K Jul 27 18:01 nodejs-logo.svg
-rwxrw-r-- 1 sammy sammy 129 Jul 27 17:56 processNodejsImage.sh
We’ve now successfully executed processNodejsImage.sh
as a child process in Node.js using the execFile()
function.
现在,我们已经使用execFile()
函数成功地将processNodejsImage.sh
作为Node.js中的子进程执行了。
The exec()
and execFile()
functions can run commands on the operating system’s shell in a Node.js child process. Node.js also provides another method with similar functionality, spawn()
. The difference is that instead of getting the output of the shell commands all at once, we get them in chunks via a stream. In the next section we’ll use the spawn()
command to create a child process.
exec()
和execFile()
函数可以在Node.js子进程的操作系统外壳上运行命令。 Node.js还提供了另一个具有类似功能的方法spawn()
。 不同之处在于,我们不是一次全部获取shell命令的输出,而是通过流将它们成块地获取。 在下一节中,我们将使用spawn()
命令创建一个子进程。
spawn()
创建子进程 (Step 2 — Creating a Child Process with spawn()
)The spawn()
function runs a command in a process. This function returns data via the stream API. Therefore, to get the output of the child process, we need to listen for stream events.
spawn()
函数在进程中运行命令。 该函数通过流API返回数据。 因此,要获取子进程的输出,我们需要监听流事件 。
Streams in Node.js are instances of event emitters. If you would like to learn more about listening for events and the foundations of interacting with streams, you can read our guide on Using Event Emitters in Node.js.
Node.js中的流是事件发射器的实例。 如果您想了解有关侦听事件以及与流进行交互的基础的更多信息,请阅读有关在Node.js中使用事件发射器的指南。
It’s often a good idea to choose spawn()
over exec()
or execFile()
when the command you want to run can output a large amount of data. With a buffer, as used by exec()
and execFile()
, all the processed data is stored in the computer’s memory. For large amounts of data, this can degrade system performance. With a stream, the data is processed and transferred in small chunks. Therefore, you can process a large amount of data without using too much memory at any one time.
当您要运行的命令可以输出大量数据时,最好是在exec()
或execFile()
上选择spawn()
。 使用exec()
和execFile()
使用的缓冲区,所有处理的数据都存储在计算机的内存中。 对于大量数据,这可能会降低系统性能。 使用流,数据将以小块的形式进行处理和传输。 因此,您可以一次处理大量数据而无需占用太多内存。
Let’s see how we can use spawn()
to make a child process. We will write a new Node.js module that creates a child process to run the find
command. We will use the find
command to list all the files in the current directory.
让我们看看如何使用spawn()
创建一个子进程。 我们将编写一个新的Node.js模块,该模块创建一个子进程来运行find
命令。 我们将使用find
命令列出当前目录中的所有文件。
Create a new file called findFiles.js
:
创建一个名为findFiles.js
的新文件:
In your text editor, begin by calling the spawn()
command:
在您的文本编辑器中,首先调用spawn()
命令:
const { spawn } = require('child_process');
const child = spawn('find', ['.']);
We first imported the spawn()
function from the child_process
module. We then called the spawn()
function to create a child process that executes the find
command. We hold the reference to the process in the child
variable, which we will use to listen to its streamed events.
我们首先从child_process
模块导入了spawn()
函数。 然后,我们调用spawn()
函数来创建一个执行find
命令的子进程。 我们在child
变量中保留对流程的引用,我们将使用该变量来监听其流事件。
The first argument in spawn()
is the command to run, in this case find
. The second argument is an array that contains the arguments for the executed command. In this case, we are telling Node.js to execute the find
command with the argument .
, thereby making the command find all the files in the current directory. The equivalent command in the terminal is find .
.
spawn()
的第一个参数是要运行的命令,在本例中为find
。 第二个参数是一个数组 ,其中包含已执行命令的参数。 在这种情况下,我们要告诉Node.js使用参数实执行find
命令.
,从而使该命令查找当前目录中的所有文件。 终端中的等效命令为find .
。
With the exec()
and execFile()
functions, we wrote the arguments along with the command in one string. However, with spawn()
, all arguments to commands must be entered in the array. That’s because spawn()
, unlike exec()
and execFile()
, does not create a new shell before running a process. To have commands with their arguments in one string, you need Node.js to create a new shell as well.
使用exec()
和execFile()
函数,我们将参数和命令一起写在一个字符串中。 但是,使用spawn()
,必须将所有命令参数输入数组。 这是因为spawn()
与exec()
和execFile()
,在运行进程之前不会创建新的shell。 要使命令的参数放在一个字符串中,您还需要Node.js来创建一个新的shell。
Let’s continue our module by adding listeners for the command’s output. Add the following highlighted lines:
让我们通过添加命令输出的侦听器来继续我们的模块。 添加以下突出显示的行:
const { spawn } = require('child_process');
const child = spawn('find', ['.']);
child.stdout.on('data', data => {
console.log(`stdout:\n${data}`);
});
child.stderr.on('data', data => {
console.error(`stderr: ${data}`);
});
Commands can return data in either the stdout
stream or the stderr
stream, so you added listeners for both. You can add listeners by calling the on()
method of each streams’ objects. The data
event from the streams gives us the command’s output to that stream. Whenever we get data on either stream, we log it to the console.
命令可以在stdout
流或stderr
流中返回数据,因此您为两者都添加了侦听器。 您可以通过调用每个流的对象的on()
方法来添加侦听器。 流中的data
事件为我们提供了该流的命令输出。 每当我们从任一流中获取数据时,我们都会将其记录到控制台。
We then listen to two other events: the error
event if the command fails to execute or is interrupted, and the close
event for when the command has finished execution, thus closing the stream.
然后,我们侦听其他两个事件:命令执行失败或中断时发生的error
事件,以及命令执行完成时的close
事件,从而关闭流。
In the text editor, complete the Node.js module by writing the following highlighted lines:
在文本编辑器中,通过编写以下突出显示的行来完成Node.js模块:
const { spawn } = require('child_process');
const child = spawn('find', ['.']);
child.stdout.on('data', (data) => {
console.log(`stdout:\n${data}`);
});
child.stderr.on('data', (data) => {
console.error(`stderr: ${data}`);
});
child.on('error', (error) => {
console.error(`error: ${error.message}`);
});
child.on('close', (code) => {
console.log(`child process exited with code ${code}`);
});
For the error
and close
events, you set up a listener directly on the child
variable. When listening for error
events, if one occurs Node.js provides an Error
object. In this case, you log the error’s message
property.
对于error
和close
事件,可以直接在child
变量上设置侦听器。 侦听error
事件时,如果发生error
,Node.js将提供一个Error
对象。 在这种情况下,您记录错误的message
属性。
When listening to the close
event, Node.js provides the exit code of the command. An exit code denotes if the command ran successfully or not. When a command runs without errors, it returns the lowest possible value for an exit code: 0
. When executed with an error, it returns a non-zero code.
侦听close
事件时,Node.js提供命令的退出代码 。 退出代码表示命令是否成功运行。 当命令运行无错误时,它返回退出代码的最低可能值: 0
。 当执行错误时,它将返回非零代码。
The module is complete. Save and exit nano
with CTRL+X
.
该模块已完成。 使用CTRL+X
保存并退出nano
。
Now, run the code with the node
command:
现在,使用node
命令运行代码:
Once complete, you will find the following output:
完成后,您将找到以下输出:
Output
stdout:
.
./findFiles.js
./listFiles.js
./nodejs-logo.svg
./processNodejsImage.sh
./getNodejsImage.js
child process exited with code 0
We find a list of all files in our current directory and the exit code of the command, which is 0
as it ran successfully. While our current directory has a small number of files, if we ran this code in our home directory, our program would list every single file in every accessible folder for our user. Because it has such a potentially large output, using the spawn()
function is most ideal as its streams do not require as much memory as a large buffer.
我们在当前目录中找到所有文件的列表以及该命令的退出代码,该命令成功运行时为0
。 虽然当前目录中的文件数量很少,但是如果我们在主目录中运行此代码,则程序将为用户列出每个可访问文件夹中的每个文件。 因为它具有潜在的大输出,所以使用spawn()
函数是最理想的,因为其流不需要像大缓冲区那样多的内存。
So far we’ve used functions to create child processes to execute external commands in our operating system. Node.js also provides a way to create a child process that executes other Node.js programs. Let’s use the fork()
function to create a child process for a Node.js module in the next section.
到目前为止,我们已经使用函数来创建子进程以在操作系统中执行外部命令。 Node.js还提供了一种创建执行其他Node.js程序的子进程的方法。 在下一节中,我们将使用fork()
函数为Node.js模块创建一个子进程。
fork()
创建子进程 (Step 3 — Creating a Child Process with fork()
)Node.js provides the fork()
function, a variation of spawn()
, to create a child process that’s also a Node.js process. The main benefit of using fork()
to create a Node.js process over spawn()
or exec()
is that fork()
enables communication between the parent and the child process.
Node.js提供了fork()
函数( spawn()
的变体spawn()
来创建也是Node.js进程的子进程。 使用fork()
在spawn()
或exec()
fork()
上创建Node.js进程的主要好处是fork()
启用了父进程和子进程之间的通信。
With fork()
, in addition to retrieving data from the child process, a parent process can send messages to the running child process. Likewise, the child process can send messages to the parent process.
使用fork()
,除了从子进程中检索数据之外,父进程还可以向正在运行的子进程发送消息。 同样,子进程可以将消息发送到父进程。
Let’s see an example where using fork()
to create a new Node.js child process can improve the performance of our application. Node.js programs run on a single process. Therefore, CPU intensive tasks like iterating over large loops or parsing large JSON files stop other JavaScript code from running. For certain applications, this is not a viable option. If a web server is blocked, then it cannot process any new incoming requests until the blocking code has completed its execution.
让我们看一个示例,其中使用fork()
创建新的Node.js子进程可以提高应用程序的性能。 Node.js程序在单个进程上运行。 因此,占用大量CPU资源的任务(例如遍历大型循环或解析大型JSON文件)会阻止其他JavaScript代码运行。 对于某些应用程序,这不是可行的选择。 如果Web服务器被阻止,则在阻止代码完成其执行之前,它无法处理任何新的传入请求。
Let’s see this in practice by creating a web server with two endpoints. One endpoint will do a slow computation that blocks the Node.js process. The other endpoint will return a JSON object saying hello
.
让我们在实践中通过创建具有两个端点的Web服务器来了解这一点。 一个端点将进行缓慢的计算,从而阻塞Node.js进程。 另一个端点将返回一个JSON对象,表示hello
。
First, create a new file called httpServer.js
, which will have the code for our HTTP server:
首先,创建一个名为httpServer.js
的新文件,该文件将包含我们的HTTP服务器的代码:
We’ll begin by setting up the HTTP server. This involves importing the http
module, creating a request listener function, creating a server object, and listening for requests on the server object. If you would like to dive deeper into creating HTTP servers in Node.js or would like a refresher, you can read our guide on How To Create a Web Server in Node.js with the HTTP Module.
我们将从设置HTTP服务器开始。 这涉及导入http
模块,创建请求侦听器功能,创建服务器对象以及侦听服务器对象上的请求。 如果您想更深入地研究如何在Node.js中创建HTTP服务器,或者想复习一下,可以阅读有关如何使用HTTP模块在Node.js中创建Web服务器的指南。
Enter the following code in your text editor to set up an HTTP server:
在文本编辑器中输入以下代码以设置HTTP服务器:
const http = require('http');
const host = 'localhost';
const port = 8000;
const requestListener = function (req, res) {};
const server = http.createServer(requestListener);
server.listen(port, host, () => {
console.log(`Server is running on http://${host}:${port}`);
});
This code sets up an HTTP server that will run at http://localhost:8000
. It uses template literals to dynamically generate that URL.
这段代码设置了一个HTTP服务器,该服务器将在http://localhost:8000
。 它使用模板文字来动态生成该URL。
Next, we will write an intentionally slow function that counts in a loop 5 billion times. Before the requestListener()
function, add the following code:
接下来,我们将编写一个故意慢的函数,该函数在循环中计数50亿次。 在requestListener()
函数之前,添加以下代码:
...
const port = 8000;
const slowFunction = () => {
let counter = 0;
while (counter < 5000000000) {
counter++;
}
return counter;
}
const requestListener = function (req, res) {};
...
This uses the arrow function syntax to create a while
loop that counts to 5000000000
.
这使用箭头函数语法创建一个while
循环 ,计数为5000000000
。
To complete this module, we need to add code to the requestListener()
function. Our function will call the slowFunction()
on subpath, and return a small JSON message for the other. Add the following code to the module:
要完成此模块,我们需要将代码添加到requestListener()
函数。 我们的函数将在子路径上调用slowFunction()
,并为另一个返回一条小的JSON消息。 将以下代码添加到模块:
...
const requestListener = function (req, res) {
if (req.url === '/total') {
let slowResult = slowFunction();
let message = `{"totalCount":${slowResult}}`;
console.log('Returning /total results');
res.setHeader('Content-Type', 'application/json');
res.writeHead(200);
res.end(message);
} else if (req.url === '/hello') {
console.log('Returning /hello results');
res.setHeader('Content-Type', 'application/json');
res.writeHead(200);
res.end(`{"message":"hello"}`);
}
};
...
If the user reaches the server at the /total
subpath, then we run slowFunction()
. If we are hit at the /hello
subpath, we return this JSON message: {"message":"hello"}
.
如果用户通过/total
子路径到达服务器,则我们运行slowFunction()
。 如果在/hello
子路径中被击中,我们将返回以下JSON消息: {"message":"hello"}
。
Save and exit the file by pressing CTRL+X
.
通过按CTRL+X
保存并退出文件。
To test, run this server module with node
:
要测试,请使用node
运行此服务器模块:
When our server starts, the console will display the following:
当我们的服务器启动时,控制台将显示以下内容:
Output
Server is running on http://localhost:8000
Now, to test the performance of our module, open two additional terminals. In the first terminal, use the curl
command to make a request to the /total
endpoint, which we expect to be slow:
现在,要测试模块的性能,请打开两个附加端子。 在第一个终端中,使用curl
命令向/total
端点发出请求,我们认为这很慢:
In the other terminal, use curl
to make a request to the /hello
endpoint like this:
在另一个终端中,使用curl
向/hello
端点发出请求,如下所示:
The first request will return the following JSON:
第一个请求将返回以下JSON:
Output
{"totalCount":5000000000}
Whereas the second request will return this JSON:
而第二个请求将返回此JSON:
Output
{"message":"hello"}
The request to /hello
completed only after the request to /total
. The slowFunction()
blocked all other code from executing while it was still in its loop. You can verify this by looking at the Node.js server output that was logged in your original terminal:
/hello
的请求仅在/total
的请求之后才完成。 slowFunction()
阻止所有其他代码在仍处于循环中时执行。 您可以通过查看原始终端中记录的Node.js服务器输出来验证这一点:
Output
Returning /total results
Returning /hello results
To process the blocking code while still accepting incoming requests, we can move the blocking code to a child process with fork()
. We will move the blocking code into its own module. The Node.js server will then create a child process when someone accesses the /total
endpoint and listen for results from this child process.
为了在仍然接受传入请求的同时处理阻塞代码,我们可以使用fork()
将阻塞代码移动到子进程中。 我们将阻塞代码移到其自己的模块中。 当有人访问/total
终结点并侦听此子进程的结果时,Node.js服务器将创建一个子进程。
Refactor the server by first creating a new module called getCount.js
that will contain slowFunction()
:
通过首先创建一个名为getCount.js
的新模块来重构服务器,该模块将包含slowFunction()
:
Now enter the code for slowFunction()
once again:
现在,再次输入slowFunction()
的代码:
const slowFunction = () => {
let counter = 0;
while (counter < 5000000000) {
counter++;
}
return counter;
}
Since this module will be a child process created with fork()
, we can also add code to communicate with the parent process when slowFunction()
has completed processing. Add the following block of code that sends a message to the parent process with the JSON to return to the user:
由于此模块将是使用fork()
创建的子进程,因此我们也可以添加代码以便在slowFunction()
完成处理后与父进程进行通信。 添加以下代码块,该代码块使用JSON向父进程发送消息以返回给用户:
const slowFunction = () => {
let counter = 0;
while (counter < 5000000000) {
counter++;
}
return counter;
}
process.on('message', (message) => {
if (message == 'START') {
console.log('Child process received START message');
let slowResult = slowFunction();
let message = `{"totalCount":${slowResult}}`;
process.send(message);
}
});
Let’s break down this block of code. The messages between a parent and child process created by fork()
are accessible via the Node.js global process
object. We add a listener to the process
variable to look for message
events. Once we receive a message
event, we check if it’s the START
event. Our server code will send the START
event when someone accesses the /total
endpoint. Upon receiving that event, we run slowFunction()
and create a JSON string with the result of the function. We use process.send()
to send a message to the parent process.
让我们分解一下这段代码。 可通过Node.js全局process
对象访问fork()
创建的父流程与子流程之间的消息。 我们向process
变量添加一个侦听器以查找message
事件。 收到message
事件后,我们将检查它是否为START
事件。 当有人访问/total
端点时,我们的服务器代码将发送START
事件。 收到该事件后,我们运行slowFunction()
并使用函数结果创建一个JSON字符串。 我们使用process.send()
将消息发送到父进程。
Save and exit getCount.js
by entering CTRL+X
in nano.
通过在nano中输入CTRL+X
保存并退出getCount.js
。
Now, let’s modify the httpServer.js
file so that instead of calling slowFunction()
, it creates a child process that executes getCount.js
.
现在,让我们修改httpServer.js
文件,以便它创建调用getCount.js
的子进程,而不是调用slowFunction()
。
Re-open httpServer.js
with nano
:
用nano
重新打开httpServer.js
:
First, import the fork()
function from the child_process
module:
首先,从child_process
模块导入fork()
函数:
const http = require('http');
const { fork } = require('child_process');
...
Next, we are going to remove the slowFunction()
from this module and modify the requestListener()
function to create a child process. Change the code in your file so it looks like this:
接下来,我们将从该模块中删除slowFunction()
并修改requestListener()
函数以创建一个子进程。 更改文件中的代码,如下所示:
...
const port = 8000;
const requestListener = function (req, res) {
if (req.url === '/total') {
const child = fork(__dirname + '/getCount');
child.on('message', (message) => {
console.log('Returning /total results');
res.setHeader('Content-Type', 'application/json');
res.writeHead(200);
res.end(message);
});
child.send('START');
} else if (req.url === '/hello') {
console.log('Returning /hello results');
res.setHeader('Content-Type', 'application/json');
res.writeHead(200);
res.end(`{"message":"hello"}`);
}
};
...
When someone goes to the /total
endpoint, we now create a new child process with fork()
. The argument of fork()
is the path to the Node.js module. In this case, it is the getCount.js
file in our current directory, which we receive from __dirname
. The reference to this child process is stored in a variable child
.
当某人转到/total
端点时,我们现在使用fork()
创建一个新的子进程。 fork()
的参数是Node.js模块的路径。 在这种情况下,它是当前目录中的getCount.js
文件,该文件是从__dirname
接收的。 对该子进程的引用存储在变量child
。
We then add a listener to the child
object. This listener captures any messages that the child process gives us. In this case, getCount.js
will return a JSON string with the total number counted by the while
loop. When we receive that message, we send the JSON to the user.
然后,我们将侦听器添加到child
对象。 该侦听器捕获子进程提供给我们的所有消息。 在这种情况下, getCount.js
将返回一个JSON字符串,其中包含while
循环计算的总数。 收到该消息后,我们会将JSON发送给用户。
We use the send()
function of the child
variable to give it a message. This program sends the message START
, which begins the execution of slowFunction()
in the child process.
我们使用child
变量的send()
函数send()
消息。 该程序发送消息START
,该消息START
在子进程中执行slowFunction()
。
Save and exit nano
by entering CTRL+X
.
通过输入CTRL+X
保存并退出nano
。
To test the improvement using fork()
made on HTTP server, begin by executing the httpServer.js
file with node
:
要使用HTTP服务器上的fork()
测试改进,首先要执行带有node
的httpServer.js
文件:
Like before, it will output the following message when it launches:
和以前一样,启动时它将输出以下消息:
Output
Server is running on http://localhost:8000
To test the server, we will need an additional two terminals as we did the first time. You can re-use them if they are still open.
要测试服务器,我们将需要像第一次一样的另外两个终端。 如果它们仍处于打开状态,则可以重新使用它们。
In the first terminal, use the curl
command to make a request to the /total
endpoint, which takes a while to compute:
在第一个终端中,使用curl
命令向/total
端点发出请求,这需要一些时间来计算:
In the other terminal, use curl
to make a request to the /hello
endpoint, which responds in a short time:
在另一个终端中,使用curl
向/hello
端点发出请求,该请求会在短时间内响应:
The first request will return the following JSON:
第一个请求将返回以下JSON:
Output
{"totalCount":5000000000}
Whereas the second request will return this JSON:
而第二个请求将返回此JSON:
Output
{"message":"hello"}
Unlike the first time we tried this, the second request to /hello
runs immediately. You can confirm by reviewing the logs, which will look like this:
与我们第一次尝试不同,对/hello
的第二个请求立即运行。 您可以通过查看日志来确认,如下所示:
Output
Child process received START message
Returning /hello results
Returning /total results
These logs show that the request for the /hello
endpoint ran after the child process was created but before the child process had finished its task.
这些日志显示对/hello
端点的请求在创建子进程之后但在子进程完成其任务之前运行。
Since we moved the blocking code in a child process using fork()
, the server was still able to respond to other requests and execute other JavaScript code. Because of the fork()
function’s message passing ability, we can control when a child process begins an activity and we can return data from a child process to a parent process.
由于我们使用fork()
在子进程中移动了阻塞代码,因此服务器仍然能够响应其他请求并执行其他JavaScript代码。 由于fork()
函数具有消息传递功能,因此我们可以控制子进程何时开始活动,并且可以将数据从子进程返回到父进程。
In this article, you used various functions to create a child process in Node.js. You first created child processes with exec()
to run shell commands from Node.js code. You then ran an executable file with the execFile()
function. You looked at the spawn()
function, which can also run commands but returns data via a stream and does not start a shell like exec()
and execFile()
. Finally, you used the fork()
function to allow for two-way communication between the parent and child process.
在本文中,您使用了各种功能在Node.js中创建子进程。 首先,您使用exec()
创建了子进程,以运行来自Node.js代码的shell命令。 然后,您可以使用execFile()
函数运行一个可执行文件。 您查看了spawn()
函数,该函数还可以运行命令,但通过流返回数据,而不启动像exec()
和execFile()
这样的shell。 最后,您使用fork()
函数允许父进程和子进程之间的双向通信。
To learn more about the child_process
module, you can read the Node.js documentation. If you’d like to continue learning Node.js, you can return to the How To Code in Node.js series, or browse programming projects and setups on our Node topic page.
要了解有关child_process
模块的更多信息,可以阅读Node.js文档 。 如果您想继续学习Node.js,可以返回“ 如何在Node.js中编码”系列 ,或者在Node主题页面上浏览编程项目和设置。
翻译自: https://www.digitalocean.com/community/tutorials/how-to-launch-child-processes-in-node-js
查看 node.js 进程