在node中使用子线程创建多任务

在node中使用子线程创建多任务

一个nodejs程序执行是从上到下每一行执行一次( 单线程 )的。现在,如果线程中有一个同步块,或者有像使用加密加密/解密那样的cpu密集操作,它将阻止进一步的执行。需要卸载类似这些任务,以保持主线程的自由。这可以通过子进程来实现,这正是这篇博客将介绍的。

process 是什么

process 不一定是特定的概念。这是我们在系统上运行程序的一个实例。打开浏览器或打开这个程序都是调用一个process

同样,在nodejs中创建的应用程序在执行时也会有一个process。我们可以使用processnodejs已经内置了process模块,不必通过 require 引入。

Child Process是什么

因此,当一个过程被执行,并且有一个需要花费一些时间才能运行的任务时,最好将任务从主线程移开,以避免任何阻塞。nodejs中的child process模块可以让我们将这些繁重的任务放到子流程中,而子流程又可以使得我们的主线程保持自由。

child process模块中,基本上有3个功能可以帮助我们创建子流程:

  • Spawn
  • Exec & ExecFile
  • Fork

Spawn

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

execspawn 有些相似。同样在一个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

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系统而言,通常情况下 binsbin 目录将有可执行文件。Windows系统通常将默认可执行文件存储在程序文件中,还可以在execFile中运行shell脚本。

execFile 不像exec 不会在shell中运行命令。相反,它将可执行文件生成为一个新的子进程,使其比exec 稍微有效一些。如果在Windows系统上运行文件有问题,可以跟spawn一样配置一个shell选项。

Fork

我们将研究的最后一个方法是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中创建子进程任务以及如何在项目中实现它们有一个基本的了解。

你可能感兴趣的:(node,javascript,node.js)