文章目录

  • 简介
  • child process
  • 异步创建进程
  • 同步创建进程

简介

nodejs的main event loop是单线程的,nodejs本身也维护着Worker Pool用来处理一些耗时的操作,我们还可以通过使用nodejs提供的worker_threads来手动创建新的线程来执行自己的任务。

本文将会介绍一种新的执行nodejs任务的方式,child process。

child process

lib/child_process.js提供了child_process模块,通过child_process我们可以创建子进程。

注意,worker_threads创建的是子线程,而child_process创建的是子进程。

在child_process模块中,可以同步创建进程也可以异步创建进程。同步创建方式只是在异步创建的方法后面加上Sync。

创建出来的进程用ChildProcess类来表示。

我们看下ChildProcess的定义:

interface ChildProcess extends events.EventEmitter {stdin: Writable | null;stdout: Readable | null;stderr: Readable | null;readonly channel?: Pipe | null;readonly stdio: [Writable | null, // stdinReadable | null, // stdoutReadable | null, // stderrReadable | Writable | null | undefined, // extraReadable | Writable | null | undefined // extra];readonly killed: boolean;readonly pid: number;readonly connected: boolean;readonly exitCode: number | null;readonly signalCode: NodeJS.Signals | null;readonly spawnargs: string[];readonly spawnfile: string;kill(signal?: NodeJS.Signals | number): boolean;send(message: Serializable, callback?: (error: Error | null) => void): boolean;send(message: Serializable, sendHandle?: SendHandle, callback?: (error: Error | null) => void): boolean;send(message: Serializable, sendHandle?: SendHandle, options?: MessageOptions, callback?: (error: Error | null) => void): boolean;disconnect(): void;unref(): void;ref(): void;/*** events.EventEmitter* 1. close* 2. disconnect* 3. error* 4. exit* 5. message*/...}

可以看到ChildProcess也是一个EventEmitter,所以它可以发送和接受event。

ChildProcess可以接收到event有5种,分别是close,disconnect,error,exit和message。

当调用父进程中的 subprocess.disconnect() 或子进程中的 process.disconnect() 后会触发 disconnect 事件。

当出现无法创建进程,无法kill进程和向子进程发送消息失败的时候都会触发error事件。

当子进程结束后时会触发exit事件。

当子进程的 stdio 流被关闭时会触发 close 事件。 注意,close事件和exit事件是不同的,因为多个进程可能共享同一个stdio,所以发送exit事件并不一定会触发close事件。

看一个close和exit的例子:

const { spawn } = require('child_process');
const ls = spawn('ls', ['-lh', '/usr']);ls.stdout.on('data', (data) => {console.log(`stdout: ${data}`);
});ls.on('close', (code) => {console.log(`子进程使用代码 ${code} 关闭所有 stdio`);
});ls.on('exit', (code) => {console.log(`子进程使用代码 ${code} 退出`);
});

最后是message事件,当子进程使用process.send() 发送消息的时候就会被触发。

ChildProcess中有几个标准流属性,分别是stderr,stdout,stdin和stdio。

stderr,stdout,stdin很好理解,分别是标准错误,标准输出和标准输入。

我们看一个stdout的使用:

const { spawn } = require('child_process');const subprocess = spawn('ls');subprocess.stdout.on('data', (data) => {console.log(`接收到数据块 ${data}`);
});

stdio实际上是stderr,stdout,stdin的集合:

readonly stdio: [Writable | null, // stdinReadable | null, // stdoutReadable | null, // stderrReadable | Writable | null | undefined, // extraReadable | Writable | null | undefined // extra];

其中stdio[0]表示的是stdin,stdio[1]表示的是stdout,stdio[2]表示的是stderr。

如果在通过stdio创建子进程的时候,这三个标准流被设置为除pipe之外的其他值,那么stdin,stdout和stderr将为null。

我们看一个使用stdio的例子:

const assert = require('assert');
const fs = require('fs');
const child_process = require('child_process');const subprocess = child_process.spawn('ls', {stdio: [0, // 使用父进程的 stdin 用于子进程。'pipe', // 把子进程的 stdout 通过管道传到父进程 。fs.openSync('err.out', 'w') // 把子进程的 stderr 定向到一个文件。]
});assert.strictEqual(subprocess.stdio[0], null);
assert.strictEqual(subprocess.stdio[0], subprocess.stdin);assert(subprocess.stdout);
assert.strictEqual(subprocess.stdio[1], subprocess.stdout);assert.strictEqual(subprocess.stdio[2], null);
assert.strictEqual(subprocess.stdio[2], subprocess.stderr);

通常情况下父进程中维护了一个对子进程的引用计数,只有在当子进程退出之后父进程才会退出。

这个引用就是ref,如果调用了unref方法,则允许父进程独立于子进程退出。

const { spawn } = require('child_process');const subprocess = spawn(process.argv[0], ['child_program.js'], {detached: true,stdio: 'ignore'
});subprocess.unref();

最后,我们看一下如何通过ChildProcess来发送消息:

subprocess.send(message[, sendHandle[, options]][, callback])

其中message就是要发送的消息,callback是发送消息之后的回调。

sendHandle比较特殊,它可以是一个TCP服务器或socket对象,通过将这些handle传递给子进程。子进程将会在message事件中,将该handle传递给Callback函数,从而可以在子进程中进行处理。

我们看一个传递TCP server的例子,首先看主进程:

const subprocess = require('child_process').fork('subprocess.js');// 打开 server 对象,并发送该句柄。
const server = require('net').createServer();
server.on('connection', (socket) => {socket.end('由父进程处理');
});
server.listen(1337, () => {subprocess.send('server', server);
});

再看子进程:

process.on('message', (m, server) => {if (m === 'server') {server.on('connection', (socket) => {socket.end('由子进程处理');});}
});

可以看到子进程接收到了server handle,并且在子进程中监听connection事件。

下面我们看一个传递socket对象的例子:

onst { fork } = require('child_process');
const normal = fork('subprocess.js', ['normal']);
const special = fork('subprocess.js', ['special']);// 开启 server,并发送 socket 给子进程。
// 使用 `pauseOnConnect` 防止 socket 在被发送到子进程之前被读取。
const server = require('net').createServer({ pauseOnConnect: true });
server.on('connection', (socket) => {// 特殊优先级。if (socket.remoteAddress === '74.125.127.100') {special.send('socket', socket);return;}// 普通优先级。normal.send('socket', socket);
});
server.listen(1337);

subprocess.js的内容:

process.on('message', (m, socket) => {if (m === 'socket') {if (socket) {// 检查客户端 socket 是否存在。// socket 在被发送与被子进程接收这段时间内可被关闭。socket.end(`请求使用 ${process.argv[2]} 优先级处理`);}}
});

主进程创建了两个subprocess,一个处理特殊的优先级, 一个处理普通的优先级。

异步创建进程

child_process模块有4种方式可以异步创建进程,分别是child_process.spawn()、child_process.fork()、child_process.exec() 和 child_process.execFile()。

先看一个各个方法的定义:

child_process.spawn(command[, args][, options])child_process.fork(modulePath[, args][, options])child_process.exec(command[, options][, callback])child_process.execFile(file[, args][, options][, callback])

其中child_process.spawn是基础,他会异步的生成一个新的进程,其他的fork,exec和execFile都是基于spawn来生成的。

fork会生成新的Node.js 进程。

exec和execFile是以新的进程执行新的命令,并且带有callback。他们的区别就在于在windows的环境中,如果要执行.bat或者.cmd文件,没有shell终端是执行不了的。这个时候就只能以exec来启动。execFile是无法执行的。

或者也可以使用spawn。

我们看一个在windows中使用spawn和exec的例子:

// 仅在 Windows 上。
const { spawn } = require('child_process');
const bat = spawn('cmd.exe', ['/c', 'my.bat']);bat.stdout.on('data', (data) => {console.log(data.toString());
});bat.stderr.on('data', (data) => {console.error(data.toString());
});bat.on('exit', (code) => {console.log(`子进程退出,退出码 ${code}`);
});
const { exec, spawn } = require('child_process');
exec('my.bat', (err, stdout, stderr) => {if (err) {console.error(err);return;}console.log(stdout);
});// 文件名中包含空格的脚本:
const bat = spawn('"my script.cmd"', ['a', 'b'], { shell: true });
// 或:
exec('"my script.cmd" a b', (err, stdout, stderr) => {// ...
});

同步创建进程

同步创建进程可以使用child_process.spawnSync()、child_process.execSync() 和 child_process.execFileSync() ,同步的方法会阻塞 Node.js 事件循环、暂停任何其他代码的执行,直到子进程退出。

通常对于一些脚本任务来说,使用同步创建进程会比较常用。

本文作者:flydean程序那些事

本文链接:http://www.flydean.com/nodejs-childprocess/

本文来源:flydean的博客

欢迎关注我的公众号:「程序那些事」最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

在nodejs中创建child process相关推荐

  1. 在nodejs中创建cluster

    文章目录 简介 cluster集群 cluster详解 cluster中的event cluster中的方法 cluster中的属性 cluster中的worker 总结 简介 在前面的文章中,我们讲 ...

  2. nodejs中使用worker_threads来创建新的线程

    文章目录 简介 worker_threads isMainThread MessageChannel parentPort和MessagePort markAsUntransferable SHARE ...

  3. nodejs入门_如何在NodeJS中使用套接字创建专业的Chat API解决方案[入门级]

    nodejs入门 Have you ever wondered how chat applications work behind the scenes? Well, today I am going ...

  4. nodejs 获取系统环境变量_Node.js中环境变量process.env的一些事详解

    前言 最近这两天在和运维GG搞部署项目的事儿.碰到一个问题就是,咱们的dev,uat,product环境的问题. 因为是前后端分离,所以在开发和部署的过程中会有对后端接口的域名的切换问题.折腾了一下午 ...

  5. 如何在文件夹中创建nodeJs项目

    打开所在要创建的项目文件夹 首先在你的电脑上必须要装有node.js,否则一切都是空谈. 在项目文件夹中创建package.json npm init 有了package.json文件,直接使用npm ...

  6. 不要在nodejs中阻塞event loop

    文章目录 简介 event loop和worker pool event loop和worker pool中的queue 阻塞event loop event loop的时间复杂度 Event Loo ...

  7. vue和react相同点_我在React和Vue中创建了相同的应用程序。 这是区别。

    vue和react相同点 by Sunil Sandhu 由Sunil Sandhu 我在React和Vue中创建了相同的应用程序. 这是区别. (I created the same app in ...

  8. nodejs命令行执行程序_在NodeJS中编写命令行应用程序

    nodejs命令行执行程序 by Peter Benjamin 彼得·本杰明(Peter Benjamin) 在NodeJS中编写命令行应用程序 (Writing Command-Line Appli ...

  9. nodejs中events模块的使用

    nodejs中events的基本使用 讲events之前我们先用js模拟实现一个EventEmitter类 源码 //订阅者集合 class EventEmitter{constructor(){th ...

最新文章

  1. s3c2410_gpio_cfgpin函数解析
  2. 说实话,你工作5年,不知道什么是Java agent技术,让我很吃惊...
  3. python 知识点记录二
  4. libxml2中文支持
  5. 新商业蓝皮书2021
  6. poj 3660 CwoContest Floyed传递闭包
  7. linux 给权限命令,Linux小白实用命令--权限设置
  8. ubuntu下修改用户的默认目录
  9. [数据仓库]大数据体系下的工作者分类
  10. ReflectionUtils.invokeMethod的作用
  11. R统计-PCA/PCoA/db-RDA/NMDS/CA/CCA/DCA等排序分析教程
  12. 影响电缆特性阻抗的三个方面
  13. Vue2+filter实现权限管理,使不同用户进入不同界面
  14. 鹰潭高通量测序建设细节概述
  15. 无痕猫页面聊天软件,不需安装
  16. 自用 学习BCR 免疫组化
  17. exercises of nginx and images,more efforts, more happiness
  18. 北京十大推动中国科技发展的人工智能实验室
  19. 行业认证标准:如何达到DISA ASD STIG规范进行软件开发
  20. Android使用XMPP框架实现即时聊天(IM)功能(Openfire + Smack)

热门文章

  1. js+Canvas实现时钟图,简单易懂
  2. Android无线Wifi开发
  3. LVM源码分析1-概览
  4. 局域网内部沟通软件要怎么选?
  5. day10 会话书城项目第四阶段
  6. Java前端上传图片到后台
  7. SSM框架整合(企业权限管理系统)
  8. 如何借鉴一个优秀的网站
  9. 网站flash黑屏问题
  10. Python简单使用正则表达式爬取古诗