nodejs中的child_process模块学习

前言

在了解child_process之前,我们先来了解几个计算机操作系统中的基本概念,以及他们之间存在的关系。

  1. cup: 计算机包含五大基本硬件运算器、控制器、存储器、输入,输出设备。运算器和控制器集成为中央处理单元即CPU(Central Processing Unit),其主要作用是执行一系列指令运算然后将结果写回。
  2. 进程: 进程是系统进行资源分配和调度的基本单位,同一时间在单个CUP上只能有一个进程运行,它会占用独立的内存。但是我们可能会想到,计算机运行的时候肯定不只是只有一个进程在运行。在现代操作系统中,所有进程会轮流使用cpu,但是由于cpu的运行效率极高,可以在多个任务间快速切换,给我们的感觉就好像多个任务在并发执行。
  3. 线程: 线程是进程中的实体,一个进程可以拥有多个线程,一个线程必须有一个父进程。线程不拥有系统资源,线程共享该进程所拥有的全部资源,但是当有其中一个线程使用某一块共享内存的时候,其他线程必须等待它结束后,才能使用这一块内存。

我们知道node是单线程运行的,当我们用node app.js启动node服务的时候会在服务器上运行一个node的进程,我们的js代码只会在其中的一个线程运行。在node的设计中就是将耗时长的操作代理给操作系统或者其他线程,这部分操作就是磁盘I/O和网络I/O等常见的异步操作,并且将这些耗时的操作从主线程上脱离。虽然node从语言层面不支持创建线程,但是我们可以通过child_process模块创建一个新的进程完成耗时耗费资源的操作,比如说要执行一段上传或下载大文件的shell脚本,然后将执行结果回传给主线程。

本文主要给大家介绍了关于Node.js中child_process模块的相关内容,在介绍child_process模块之前,先来看一个例子。

const http = require('http');
const longComputation = () => {
    
    
    let sum = 0;
    for (let i = 0; i < 1e10; i++) {
    
    
        sum += i;
    };
    return sum;
};
const server = http.createServer();
server.on('request', (req, res) => {
    
    
    if (req.url === '/compute') {
    
    
        const sum = longComputation();
        return res.end(`Sum is ${
      
      sum}`);
    } else {
    
    
        res.end('Ok')
    }
});

server.listen(3000);

可以试一下使用上面的代码启动Node.js服务,然后打开两个浏览器选项卡分别访问/compute和/,可以发现node服务接收到/compute请求时会进行大量的数值计算,导致无法响应其他的请求(/)。
在Java语言中可以通过多线程的方式来解决上述的问题,但是Node.js在代码执行的时候是单线程的,那么Node.js应该如何解决上面的问题呢?其实Node.js可以创建一个子进程执行密集的cpu计算任务(例如上面例子中的longComputation)来解决问题,而child_process模块正是用来创建子进程的。

child_process提供了几种创建子进程的方式

  • 异步方式:spawnexecexecFilefork
  • 同步方式:spawnSyncexecSyncexecFileSync

exec

语法:child_process.exec(command[, options][, callback])

这里的第一个参数 command 就是在 shell 中执行的命令;options 可以设置与执行命令相关的参数,如:cwd(当前工作目录)、shell(执行命令的shell)、uid、gid、encoding等;callback 在命令执行完调用,可通过回调函数的 stdout 获取命令输出。options 和 callback 都是可选参数。比如想在 /Usrs/ben 目录下执行 “ls -l”,那么代码如下:

const {
    
     exec } = require('child_process');

exec('ls -l',{
    
    cwd: '/Users/liu/Desktop'}, (error, stdout, stderr) => {
    
    
  if (error) return;
  console.log('stdout:', stdout);
})

// stdout: total 4792
// -rw-r--r--@ 1 liuchongyang  staff   53248 May 24  2022 10起诉状(一审).doc
// -rw-r--r--@ 1 liuchongyang  staff   34816 May 24  2022 11答辩状(一审).doc
// -rw-r--r--@ 1 liuchongyang  staff   33280 May 24  2022 12质证意见(一审).doc
// -rw-r--r--@ 1 liuchongyang  staff   65024 May 24  2022 13代理词(一审营).doc

execFile

语法:child_process.execFile(file[, args][, options][, callback])

execFile 顾名思义就是执行可执行的文件。通常在 Unix 类型的操作系统中,execFile 相比 exec 执行会更加高效,因为其不产生 shell。在 Windows 系统中,由于 .bat.cmd 文件在没有终端的情况下不能单独执行的,所以不能使用 execFile 来执行,而应该使用 exec 或下面介绍的 spawn 来执行。

execFile 方法中 file 参数是必传,指定要执行的文件;args 可选,是给执行文件传的参数列表;options 和 callback 和 exec中的类似,就不细说了。下面,我们想看一下 node 版本,代码可如下:

const {
    
     execFile } = require('child_process');

execFile('/usr/local/bin/node', ['-v'], (error, stdout, stderr) => {
    
    
  if (error) return;
  console.log('stdout:', stdout);
})

// stdout: v12.17.0

spawn

语法:child_process.spawn(command[, args][, options])

spawnexec特性类似,都是执行一个命令,但是 spawn 并没有以回调函数的形式来接收 stdout,而是通过子进程对象上 stdout 监听 data 事件来获取标准输出数据。这样的方式使 stdout 以流的形式传输,相比 exec 要等输出结束之后才会调用回调的方式,要高效很多。

扫描二维码关注公众号,回复: 14876789 查看本文章

我们用 exec 中举的例子,在 /Usrs/ben 目录下执行 “ls -l”,那么代码如下:

const {
    
     spawn } = require('child_process');

const subprocess = spawn('ls', ['-l'], {
    
    cwd: '/Users/ben'});

subprocess.stdout.on('data', (data) => {
    
    
  console.log(data.toString());
});

// total 8
// drwx------@  7 ben  staff   224  7 28 09:36 Applications
// drwx------@ 25 ben  staff   800  7 31 22:19 Desktop
// drwx------@ 24 ben  staff   768  6 21 17:18 Documents
// drwx------@ 79 ben  staff  2528  7 31 20:25 Downloads
// ...

fork

语法:child_process.fork(modulePath[, args][, options])

fork 其实是 spawn 的一个特殊例子,因为 fork() 第一个参数是一个 node module path。args 和 options 参数都和 spawn 一致。但是 fork 执行的是一个 node module,所以 fork 提供了一个特性,即在父子进程之间建立一个 IPC 通道,使父子进程之间通过 send() 方法来互相发送信息。如下例子:

child.js

//child.js
process.on('message',function(msg){
    
    
   console.log('child receive msg:', msg) // child receive msg: hello world
   process.send(msg)
})

parent.js

// parent.js
let cp=require('child_process');
let child=cp.fork('./child');
child.on('message',function(msg){
    
    
  console.log('parent get messg:',msg); // parent get messg: hello world
});
child.send('hello world');

因为 fork 会在父子进程间建立通信通道,所以如果有同步的 fork,那么这个 IPC 通道就不会存在,所以 fork 没有对应的同步方法。

总结

四种创建子进程的方法中 spawn 和 fork 要相对常用一些,spawn 处理操作系统命令;fork 处理 node module(并且父子进程间会建立 IPC 通道进行通信);exec 是直接使用 shell 执行命令,所以可以方便使用 shell 中的管道等特性,但是输出结果会在回调中一次输出,所以不适合输出数据特别大的情况;execFile 不适合 Windows 系统。

猜你喜欢

转载自blog.csdn.net/woyebuzhidao321/article/details/129567942
今日推荐