关于node.js:深入理解Nodejs的进程与子进程

45次阅读

共计 5850 个字符,预计需要花费 15 分钟才能阅读完成。

过程:process 模块

process 模块是 nodejs 提供给开发者用来和以后过程交互的工具,它的提供了很多实用的 API。从文档登程,管中窥豹,进一步意识和学习 process 模块:

  • 如何解决命令参数?
  • 如何解决工作目录?
  • 如何解决异样?
  • 如何解决过程退出?
  • process 的规范流对象
  • 深刻了解 process.nextTick

如何解决命令参数?

命令行参数指的是 2 个方面:

  • 传给 node 的参数。例如 node --harmony script.js --version 中,--harmony 就是传给 node 的参数
  • 传给过程的参数。例如 node script.js --version --help 中,--version --help 就是传给过程的参数

它们别离通过 process.argvprocess.execArgv 来取得。

如何解决工作目录?

通过 process.cwd() 能够获取以后的工作目录。

通过 process.chdir(directory) 能够切换以后的工作目录,失败后会抛出异样。实际如下:

function safeChdir(dir) {
    try {process.chdir(dir);
        return true;
    } catch (error) {return false;}
}

如何解决异样?

uncaughtException 事件

Nodejs 能够通过 try-catch 来捕捉异样。如果异样未捕捉,则会始终从底向事件循环冒泡。如是冒泡到事件循环的异样没被解决,那么就会导致以后过程异样退出。

依据文档,能够通过监听 process 的 uncaughtException 事件,来解决未捕捉的异样:

process.on("uncaughtException", (err, origin) => {console.log(err.message);
});

const a = 1 / b;
console.log("abc"); // 不会执行

下面的代码,控制台的输入是:b is not defined。捕捉了错误信息,并且过程以 0 退出。开发者能够在 uncaughtException 事件中,革除一些曾经调配的资源(文件描述符、句柄等),不举荐在其中重启过程。

unhandledRejection 事件

如果一个 Promise 回调的异样没有被 .catch() 捕捉,那么就会触发 process 的 unhandledRejection 事件:

process.on("unhandledRejection", (err, promise) => {console.log(err.message);
});

Promise.reject(new Error("错误信息")); // 未被 catch 捕捉的异样,交由 unhandledRejection 事件处理

warning 事件

告警不是 Node.js 和 Javascript 错误处理流程的正式组成部分。一旦探测到可能导致利用性能问题,缺点或安全隐患相干的代码实际,Node.js 就可收回告警。

比方前一段代码中,如果呈现未被捕捉的 promise 回调的异样,那么就会触发 warning 事件。

如何解决过程退出?

process.exit() vs process.exitCode

一个 nodejs 过程,能够通过 process.exit() 来指定退出代码,间接退出。不举荐间接应用 process.exit(),这会导致事件循环中的工作间接不被解决,以及可能导致数据的截断和失落(例如 stdout 的写入)。

setTimeout(() => {console.log("我不会执行");
});

process.exit(0);

正确平安的解决是,设置 process.exitCode,并容许过程天然退出。

setTimeout(() => {console.log("我不会执行");
});

process.exitCode = 1;

beforeExit 事件

用于解决过程退出的事件有:beforeExit 事件 和 exit 事件。

当 Node.js 清空其事件循环并且没有其余工作要安顿时,会触发 beforeExit 事件。例如在退出前须要一些异步操作,那么能够写在 beforeExit 事件中:

let hasSend = false;
process.on("beforeExit", () => {if (hasSend) return; // 防止死循环

    setTimeout(() => {console.log("mock send data to serve");
        hasSend = true;
    }, 500);
});

console.log(".......");
// 输入:// .......
// mock send data to serve

留神:在 beforeExit 事件中如果是异步工作,那么又会被增加到工作队列。此时,工作队列实现所有工作后,又回触发 beforeExit 事件。因而,不解决的话,可能呈现死循环的状况。如果是显式调用 exit(),那么不会触发此事件。

exit 事件

在 exit 事件中,只能执行同步操作。在调用 ‘exit’ 事件监听器之后,Node.js 过程将立刻退出,从而导致在事件循环中仍排队的任何其余工作被放弃。参考 nodejs 进阶视频解说:进入学习

process 的规范流对象

process 提供了 3 个规范流。须要留神的是,它们有些在某些时候是同步阻塞的(请见文档)。

  • process.stderr:WriteStream 类型,console.error的底层实现,默认对应屏幕
  • process.stdout:WriteStream 类型,console.log的底层实现,默认对应屏幕
  • process.stdin:ReadStream 类型,默认对应键盘输入

上面是基于“生产者 - 消费者模型”的读取控制台输出并且及时输入的代码:

process.stdin.setEncoding("utf8");

process.stdin.on("readable", () => {
    let chunk;
    while ((chunk = process.stdin.read()) !== null) {process.stdout.write(`>>> ${chunk}`);
    }
});

process.stdin.on("end", () => {process.stdout.write("完结");
});

对于事件的含意,还是请看 stream 的文档。

深刻了解 process.nextTick

我第一次看到 process.nextTick 的时候是比拟懵的,看文档能够晓得,它的用处是:把回调函数作为微工作,放入事件循环的工作队列中。但这么做的意义是什么呢?

因为 nodejs 并不适宜计算密集型的利用,一个过程就一个线程,在当下工夫点上,就一个事件在执行。那么,如果咱们的事件占用了很多 cpu 工夫,那么之后的事件就要期待十分久。所以,nodejs 的一个编程准则是尽量缩短每一个事件的执行事件 。process.nextTick 的作用就在这, 将一个大的工作分解成多个小的工作。示例代码如下:

// 被拆分成 2 个函数执行
function BigThing() {doPartThing();

    process.nextTick(() => finishThing());
}

在事件循环中,何时执行 nextTick 注册的工作呢?请看上面的代码:

setTimeout(function() {console.log("第一个 1 秒");
    process.nextTick(function() {console.log("第一个 1 秒:nextTick");
    });
}, 1000);

setTimeout(function() {console.log("第 2 个 1 秒");
}, 1000);

console.log("我要输入 1");

process.nextTick(function() {console.log("nextTick");
});

console.log("我要输入 2");

输入的后果如下,nextTick 是早于 setTimeout:

我要输入 1
我要输入 2
nextTick
第一个 1 秒
第一个 1 秒:nextTick
第 2 个 1 秒

在浏览器端,nextTick 会进化成 setTimeout(callback, 0)。但在 nodejs 中请应用 nextTick 而不是 setTimeout,前者效率更高,并且严格来说,两者创立的事件在工作队列中程序并不一样(请看后面的代码)。

子过程:child_process 模块

把握 nodejs 的 child_process 模块可能极大进步 nodejs 的开发能力,例如主从过程来优化 CPU 计算的问题,多过程开发等等。本文从以下几个方面介绍 child_process 模块的应用:

  • 创立子过程
  • 父子过程通信
  • 独立子过程
  • 过程管道

创立子过程

nodejs 的 child_process 模块创立子过程的办法:spawn, fork, exec, execFile。它们的关系如下:

  • fork, exec, execFile 都是通过 spawn 来实现的。
  • exec 默认会创立 shell。execFile 默认不会创立 shell,意味着不能应用 I/O 重定向、file glob,但效率更高。
  • spawn、exec、execFile 都有同步版本,可能会造成过程阻塞。

child_process.spawn()的应用:

const {spawn} = require("child_process");
// 返回 ChildProcess 对象,默认状况下其上的 stdio 不为 null
const ls = spawn("ls", ["-lh"]);

ls.stdout.on("data", data => {console.log(`stdout: ${data}`);
});

ls.stderr.on("data", data => {console.error(`stderr: ${data}`);
});

ls.on("close", code => {console.log(` 子过程退出,退出码 ${code}`);
});

child_process.exec()的应用:

const {exec} = require("child_process");
// 通过回调函数来操作 stdio
exec("ls -lh", (err, stdout, stderr) => {if (err) {console.error(` 执行的谬误: ${err}`);
        return;
    }
    console.log(`stdout: ${stdout}`);
    console.error(`stderr: ${stderr}`);
});

父子过程通信

fork()返回的 ChildProcess 对象,监听其上的 message 事件,来承受子过程音讯;调用 send 办法,来实现 IPC。

parent.js 代码如下:

const {fork} = require("child_process");
const cp = fork("./sub.js");
cp.on("message", msg => {console.log("父过程收到音讯:", msg);
});
cp.send("我是父过程");

sub.js 代码如下:

process.on("message", m => {console.log("子过程收到音讯:", m);
});

process.send("我是子过程");

运行后后果:

父过程收到音讯:我是子过程
子过程收到音讯:我是父过程

独立子过程

在失常状况下,父过程肯定会期待子过程退出后,才退出。如果想让父过程先退出,不受到子过程的影响,那么应该:

  • 调用 ChildProcess 对象上的unref()
  • options.detached 设置为 true
  • 子过程的 stdio 不能是连贯到父过程

main.js 代码如下:

const {spawn} = require("child_process");
const subprocess = spawn(process.argv0, ["sub.js"], {
    detached: true,
    stdio: "ignore"
});

subprocess.unref();

sub.js 代码如下:

setInterval(() => {}, 1000);

过程管道

options.stdio 选项用于配置在父过程和子过程之间建设的管道。默认状况下,子过程的 stdin、stdout 和 stderr 会被重定向到 ChildProcess 对象上相应的 subprocess.stdin、subprocess.stdout 和 subprocess.stderr 流。这意味着能够通过监听其上的 data事件,在父过程中获取子过程的 I/O。

能够用来实现“重定向”:

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 定向到一个文件。]
});

也能够用来实现 ” 管道运算符 ”:

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

const ps = spawn("ps", ["ax"]);
const grep = spawn("grep", ["ssh"]);

ps.stdout.on("data", data => {grep.stdin.write(data);
});

ps.stderr.on("data", err => {console.error(`ps stderr: ${err}`);
});

ps.on("close", code => {if (code !== 0) {console.log(`ps 过程退出,退出码 ${code}`);
    }
    grep.stdin.end();});

grep.stdout.on("data", data => {console.log(data.toString());
});

grep.stderr.on("data", data => {console.error(`grep stderr: ${data}`);
});

grep.on("close", code => {if (code !== 0) {console.log(`grep 过程退出,退出码 ${code}`);
    }
});

正文完
 0