溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Node.js中的進(jìn)程與子進(jìn)程怎么處理

發(fā)布時(shí)間:2022-11-18 09:43:01 來源:億速云 閱讀:111 作者:iii 欄目:web開發(fā)

這篇文章主要講解了“Node.js中的進(jìn)程與子進(jìn)程怎么處理”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“Node.js中的進(jìn)程與子進(jìn)程怎么處理”吧!

進(jìn)程:process模塊

process 模塊是 nodejs 提供給開發(fā)者用來和當(dāng)前進(jìn)程交互的工具,它的提供了很多實(shí)用的 API。

從文檔出發(fā),管中窺豹,進(jìn)一步認(rèn)識(shí)和學(xué)習(xí) process 模塊:

  • 如何處理命令參數(shù)?

  • 如何處理工作目錄?

  • 如何處理異常?

  • 如何處理進(jìn)程退出?

  • process 的標(biāo)準(zhǔn)流對(duì)象

  • 深入理解 process.nextTick

如何處理命令參數(shù)?

命令行參數(shù)指的是 2 個(gè)方面:

  • 傳給 node 的參數(shù)。例如 node --harmony script.js --version 中,--harmony 就是傳給 node 的參數(shù)

  • 傳給進(jìn)程的參數(shù)。例如 node script.js --version --help 中,--version --help 就是傳給進(jìn)程的參數(shù)

它們分別通過 process.argvprocess.execArgv 來獲得。

如何處理工作目錄?

通過process.cwd()可以獲取當(dāng)前的工作目錄。

通過process.chdir(directory)可以切換當(dāng)前的工作目錄,失敗后會(huì)拋出異常。實(shí)踐如下:

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

如何處理異常?

uncaughtException 事件

Nodejs 可以通過 try-catch 來捕獲異常。如果異常未捕獲,則會(huì)一直從底向事件循環(huán)冒泡。如是冒泡到事件循環(huán)的異常沒被處理,那么就會(huì)導(dǎo)致當(dāng)前進(jìn)程異常退出。

根據(jù)文檔,可以通過監(jiān)聽 process 的 uncaughtException 事件,來處理未捕獲的異常:

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

const a = 1 / b;
console.log("abc"); // 不會(huì)執(zhí)行

上面的代碼,控制臺(tái)的輸出是:b is not defined。捕獲了錯(cuò)誤信息,并且進(jìn)程以0退出。開發(fā)者可以在 uncaughtException 事件中,清除一些已經(jīng)分配的資源(文件描述符、句柄等),不推薦在其中重啟進(jìn)程。

unhandledRejection 事件

如果一個(gè) Promise 回調(diào)的異常沒有被.catch()捕獲,那么就會(huì)觸發(fā) process 的 unhandledRejection 事件:

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

Promise.reject(new Error("錯(cuò)誤信息")); // 未被catch捕獲的異常,交由unhandledRejection事件處理

warning 事件

告警不是 Node.js 和 Javascript 錯(cuò)誤處理流程的正式組成部分。 一旦探測(cè)到可能導(dǎo)致應(yīng)用性能問題,缺陷或安全隱患相關(guān)的代碼實(shí)踐,Node.js 就可發(fā)出告警。

比如前一段代碼中,如果出現(xiàn)未被捕獲的 promise 回調(diào)的異常,那么就會(huì)觸發(fā) warning 事件。參考nodejs進(jìn)階視頻講解:進(jìn)入學(xué)習(xí)

如何處理進(jìn)程退出?

process.exit() vs process.exitCode

一個(gè) nodejs 進(jìn)程,可以通過 process.exit() 來指定退出代碼,直接退出。不推薦直接使用 process.exit(),這會(huì)導(dǎo)致事件循環(huán)中的任務(wù)直接不被處理,以及可能導(dǎo)致數(shù)據(jù)的截?cái)嗪蛠G失(例如 stdout 的寫入)。

setTimeout(() => {
    console.log("我不會(huì)執(zhí)行");
});

process.exit(0);

正確安全的處理是,設(shè)置 process.exitCode,并允許進(jìn)程自然退出。

setTimeout(() => {
    console.log("我不會(huì)執(zhí)行");
});

process.exitCode = 1;

beforeExit 事件

用于處理進(jìn)程退出的事件有:beforeExit 事件 和 exit 事件。

當(dāng) Node.js 清空其事件循環(huán)并且沒有其他工作要安排時(shí),會(huì)觸發(fā) beforeExit 事件。例如在退出前需要一些異步操作,那么可以寫在 beforeExit 事件中:

let hasSend = false;
process.on("beforeExit", () => {
    if (hasSend) return; // 避免死循環(huán)

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

console.log(".......");
// 輸出:
// .......
// mock send data to serve

注意:在 beforeExit 事件中如果是異步任務(wù),那么又會(huì)被添加到任務(wù)隊(duì)列。此時(shí),任務(wù)隊(duì)列完成所有任務(wù)后,又回觸發(fā) beforeExit 事件。因此,不處理的話,可能出現(xiàn)死循環(huán)的情況。如果是顯式調(diào)用 exit(),那么不會(huì)觸發(fā)此事件。

exit 事件

在 exit 事件中,只能執(zhí)行同步操作。在調(diào)用 'exit' 事件監(jiān)聽器之后,Node.js 進(jìn)程將立即退出,從而導(dǎo)致在事件循環(huán)中仍排隊(duì)的任何其他工作被放棄。

process 的標(biāo)準(zhǔn)流對(duì)象

process 提供了 3 個(gè)標(biāo)準(zhǔn)流。需要注意的是,它們有些在某些時(shí)候是同步阻塞的(請(qǐng)見文檔)。

  • process.stderr:WriteStream 類型,console.error的底層實(shí)現(xiàn),默認(rèn)對(duì)應(yīng)屏幕

  • process.stdout:WriteStream 類型,console.log的底層實(shí)現(xiàn),默認(rèn)對(duì)應(yīng)屏幕

  • process.stdin:ReadStream 類型,默認(rèn)對(duì)應(yīng)鍵盤輸入

下面是基于“生產(chǎn)者-消費(fèi)者模型”的讀取控制臺(tái)輸入并且及時(shí)輸出的代碼:

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("結(jié)束");
});

關(guān)于事件的含義,還是請(qǐng)看stream 的文檔。

深入理解 process.nextTick

我第一次看到 process.nextTick 的時(shí)候是比較懵的,看文檔可以知道,它的用途是:把回調(diào)函數(shù)作為微任務(wù),放入事件循環(huán)的任務(wù)隊(duì)列中。但這么做的意義是什么呢?

因?yàn)?nodejs 并不適合計(jì)算密集型的應(yīng)用,一個(gè)進(jìn)程就一個(gè)線程,在當(dāng)下時(shí)間點(diǎn)上,就一個(gè)事件在執(zhí)行。那么,如果我們的事件占用了很多 cpu 時(shí)間,那么之后的事件就要等待非常久。所以,nodejs 的一個(gè)編程原則是盡量縮短每一個(gè)事件的執(zhí)行事件。process.nextTick 的作用就在這,將一個(gè)大的任務(wù)分解成多個(gè)小的任務(wù)。示例代碼如下:

// 被拆分成2個(gè)函數(shù)執(zhí)行
function BigThing() {
    doPartThing();

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

在事件循環(huán)中,何時(shí)執(zhí)行 nextTick 注冊(cè)的任務(wù)呢?請(qǐng)看下面的代碼:

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

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

console.log("我要輸出1");

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

console.log("我要輸出2");

輸出的結(jié)果如下,nextTick 是早于 setTimeout:

我要輸出1
我要輸出2
nextTick
第一個(gè)1秒
第一個(gè)1秒:nextTick
第2個(gè)1秒

在瀏覽器端,nextTick 會(huì)退化成 setTimeout(callback, 0)。但在 nodejs 中請(qǐng)使用 nextTick 而不是 setTimeout,前者效率更高,并且嚴(yán)格來說,兩者創(chuàng)建的事件在任務(wù)隊(duì)列中順序并不一樣(請(qǐng)看前面的代碼)。

子進(jìn)程:child_process模塊

掌握 nodejs 的 child_process 模塊能夠極大提高 nodejs 的開發(fā)能力,例如主從進(jìn)程來優(yōu)化 CPU 計(jì)算的問題,多進(jìn)程開發(fā)等等。本文從以下幾個(gè)方面介紹 child_process 模塊的使用:

  • 創(chuàng)建子進(jìn)程

  • 父子進(jìn)程通信

  • 獨(dú)立子進(jìn)程

  • 進(jìn)程管道

創(chuàng)建子進(jìn)程

nodejs 的 child_process 模塊創(chuàng)建子進(jìn)程的方法:spawn, fork, exec, execFile。它們的關(guān)系如下:

  • fork, exec, execFile 都是通過 spawn 來實(shí)現(xiàn)的。

  • exec 默認(rèn)會(huì)創(chuàng)建 shell。execFile 默認(rèn)不會(huì)創(chuàng)建 shell,意味著不能使用 I/O 重定向、file glob,但效率更高。

  • spawn、exec、execFile 都有同步版本,可能會(huì)造成進(jìn)程阻塞。

child_process.spawn()的使用:

const { spawn } = require("child_process");
// 返回ChildProcess對(duì)象,默認(rèn)情況下其上的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(`子進(jìn)程退出,退出碼 ${code}`);
});

child_process.exec()的使用:

const { exec } = require("child_process");
// 通過回調(diào)函數(shù)來操作stdio
exec("ls -lh", (err, stdout, stderr) => {
    if (err) {
        console.error(`執(zhí)行的錯(cuò)誤: ${err}`);
        return;
    }
    console.log(`stdout: ${stdout}`);
    console.error(`stderr: ${stderr}`);
});

父子進(jìn)程通信

fork()返回的 ChildProcess 對(duì)象,監(jiān)聽其上的 message 事件,來接受子進(jìn)程消息;調(diào)用 send 方法,來實(shí)現(xiàn) IPC。

parent.js 代碼如下:

const { fork } = require("child_process");
const cp = fork("./sub.js");
cp.on("message", msg => {
    console.log("父進(jìn)程收到消息:", msg);
});
cp.send("我是父進(jìn)程");

sub.js 代碼如下:

process.on("message", m => {
    console.log("子進(jìn)程收到消息:", m);
});

process.send("我是子進(jìn)程");

運(yùn)行后結(jié)果:

父進(jìn)程收到消息: 我是子進(jìn)程
子進(jìn)程收到消息: 我是父進(jìn)程

獨(dú)立子進(jìn)程

在正常情況下,父進(jìn)程一定會(huì)等待子進(jìn)程退出后,才退出。如果想讓父進(jìn)程先退出,不受到子進(jìn)程的影響,那么應(yīng)該:

  • 調(diào)用 ChildProcess 對(duì)象上的unref()

  • options.detached 設(shè)置為 true

  • 子進(jìn)程的 stdio 不能是連接到父進(jìn)程

main.js 代碼如下:

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

subprocess.unref();

sub.js 代碼如下:

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

進(jìn)程管道

options.stdio 選項(xiàng)用于配置在父進(jìn)程和子進(jìn)程之間建立的管道。 默認(rèn)情況下,子進(jìn)程的 stdin、 stdout 和 stderr 會(huì)被重定向到 ChildProcess 對(duì)象上相應(yīng)的 subprocess.stdin、subprocess.stdout 和 subprocess.stderr 流。 這意味著可以通過監(jiān)聽其上的 data事件,在父進(jìn)程中獲取子進(jìn)程的 I/O 。

可以用來實(shí)現(xiàn)“重定向”:

const fs = require("fs");
const child_process = require("child_process");

const subprocess = child_process.spawn("ls", {
    stdio: [
        0, // 使用父進(jìn)程的 stdin 用于子進(jìn)程。
        "pipe", // 把子進(jìn)程的 stdout 通過管道傳到父進(jìn)程 。
        fs.openSync("err.out", "w") // 把子進(jìn)程的 stderr 定向到一個(gè)文件。
    ]
});

也可以用來實(shí)現(xiàn)"管道運(yùn)算符":

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 進(jìn)程退出,退出碼 ${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 進(jìn)程退出,退出碼 ${code}`);
    }
});

感謝各位的閱讀,以上就是“Node.js中的進(jìn)程與子進(jìn)程怎么處理”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對(duì)Node.js中的進(jìn)程與子進(jìn)程怎么處理這一問題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI