国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > JavaScript > 正文

Node.js中的child_process模塊詳解

2019-11-19 13:40:58
字體:
供稿:網(wǎng)友

前言

本文主要給大家介紹了關(guān)于Node.js中child_process模塊的相關(guān)內(nèi)容,在介紹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服務(wù),然后打開兩個瀏覽器選項卡分別訪問/compute和/,可以發(fā)現(xiàn)node服務(wù)接收到/compute請求時會進(jìn)行大量的數(shù)值計算,導(dǎo)致無法響應(yīng)其他的請求(/)。

在Java語言中可以通過多線程的方式來解決上述的問題,但是Node.js在代碼執(zhí)行的時候是單線程的,那么Node.js應(yīng)該如何解決上面的問題呢?其實Node.js可以創(chuàng)建一個子進(jìn)程執(zhí)行密集的cpu計算任務(wù)(例如上面例子中的longComputation)來解決問題,而child_process模塊正是用來創(chuàng)建子進(jìn)程的。

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

child_process提供了幾種創(chuàng)建子進(jìn)程的方式

  • 異步方式:spawn、exec、execFile、fork
  • 同步方式:spawnSync、execSync、execFileSync

首先介紹一下spawn方法

child_process.spawn(command[, args][, options])command: 要執(zhí)行的指令args: 傳遞參數(shù)options: 配置項
const { spawn } = require('child_process');const child = spawn('pwd');

pwd是shell的命令,用于獲取當(dāng)前的目錄,上面的代碼執(zhí)行完控制臺并沒有任何的信息輸出,這是為什么呢?

控制臺之所以不能看到輸出信息的原因是由于子進(jìn)程有自己的stdio流(stdin、stdout、stderr),控制臺的輸出是與當(dāng)前進(jìn)程的stdio綁定的,因此如果希望看到輸出信息,可以通過在子進(jìn)程的stdout 與當(dāng)前進(jìn)程的stdout之間建立管道實現(xiàn)

child.stdout.pipe(process.stdout);

也可以監(jiān)聽事件的方式(子進(jìn)程的stdio流都是實現(xiàn)了EventEmitter API的,所以可以添加事件監(jiān)聽)

child.stdout.on('data', function(data) { process.stdout.write(data);});

在Node.js代碼里使用的console.log其實底層依賴的就是process.stdout

除了建立管道之外,還可以通過子進(jìn)程和當(dāng)前進(jìn)程共用stdio的方式來實現(xiàn)

const { spawn } = require('child_process');const child = spawn('pwd', { stdio: 'inherit'});

stdio選項用于配置父進(jìn)程和子進(jìn)程之間建立的管道,由于stdio管道有三個(stdin, stdout, stderr)因此stdio的三個可能的值其實是數(shù)組的一種簡寫

  • pipe 相當(dāng)于['pipe', 'pipe', 'pipe'](默認(rèn)值)
  • ignore 相當(dāng)于['ignore', 'ignore', 'ignore']
  • inherit 相當(dāng)于[process.stdin, process.stdout, process.stderr]

由于inherit方式使得子進(jìn)程直接使用父進(jìn)程的stdio,因此可以看到輸出

ignore用于忽略子進(jìn)程的輸出(將/dev/null指定為子進(jìn)程的文件描述符了),因此當(dāng)ignore時child.stdout是null。

spawn默認(rèn)情況下并不會創(chuàng)建子shell來執(zhí)行命令,因此下面的代碼會報錯

const { spawn } = require('child_process');const child = spawn('ls -l');child.stdout.pipe(process.stdout);// 報錯events.js:167  throw er; // Unhandled 'error' event  ^Error: spawn ls -l ENOENT at Process.ChildProcess._handle.onexit (internal/child_process.js:229:19) at onErrorNT (internal/child_process.js:406:16) at process._tickCallback (internal/process/next_tick.js:63:19) at Function.Module.runMain (internal/modules/cjs/loader.js:746:11) at startup (internal/bootstrap/node.js:238:19) at bootstrapNodeJSCore (internal/bootstrap/node.js:572:3)Emitted 'error' event at: at Process.ChildProcess._handle.onexit (internal/child_process.js:235:12) at onErrorNT (internal/child_process.js:406:16) [... lines matching original stack trace ...] at bootstrapNodeJSCore (internal/bootstrap/node.js:572:3)

如果需要傳遞參數(shù)的話,應(yīng)該采用數(shù)組的方式傳入

const { spawn } = require('child_process');const child = spawn('ls', ['-l']);child.stdout.pipe(process.stdout);

如果要執(zhí)行l(wèi)s -l | wc -l命令的話可以采用創(chuàng)建兩個spawn命令的方式

const { spawn } = require('child_process');const child = spawn('ls', ['-l']);const child2 = spawn('wc', ['-l']);child.stdout.pipe(child2.stdin);child2.stdout.pipe(process.stdout);

也可以使用exec

const { exec } = require('child_process');exec('ls -l | wc -l', function(err, stdout, stderr) { console.log(stdout);});

由于exec會創(chuàng)建子shell,所以可以直接執(zhí)行shell管道命令。spawn采用流的方式來輸出命令的執(zhí)行結(jié)果,而exec也是將命令的執(zhí)行結(jié)果緩存起來統(tǒng)一放在回調(diào)函數(shù)的參數(shù)里面,因此exec只適用于命令執(zhí)行結(jié)果數(shù)據(jù)小的情況。

其實spawn也可以通過配置shell option的方式來創(chuàng)建子shell進(jìn)而支持管道命令,如下所示

const { spawn, execFile } = require('child_process');const child = spawn('ls -l | wc -l', { shell: true});child.stdout.pipe(process.stdout);

配置項除了stdio、shell之外還有cwd、env、detached等常用的選項

cwd用于修改命令的執(zhí)行目錄

const { spawn, execFile, fork } = require('child_process');const child = spawn('ls -l | wc -l', { shell: true, cwd: '/usr'});child.stdout.pipe(process.stdout);

env用于指定子進(jìn)程的環(huán)境變量(如果不指定的話,默認(rèn)獲取當(dāng)前進(jìn)程的環(huán)境變量)

const { spawn, execFile, fork } = require('child_process');const child = spawn('echo $NODE_ENV', { shell: true, cwd: '/usr'});child.stdout.pipe(process.stdout);NODE_ENV=randal node b.js// 輸出結(jié)果randal

如果指定env的話就會覆蓋掉默認(rèn)的環(huán)境變量,如下

const { spawn, execFile, fork } = require('child_process');spawn('echo $NODE_TEST $NODE_ENV', { shell: true, stdio: 'inherit', cwd: '/usr', env: { NODE_TEST: 'randal-env' }});NODE_ENV=randal node b.js// 輸出結(jié)果randal

detached用于將子進(jìn)程與父進(jìn)程斷開連接

例如假設(shè)存在一個長時間運行的子進(jìn)程

// timer.jswhile(true) {}

但是主進(jìn)程并不需要長時間運行的話就可以用detached來斷開二者之間的連接

const { spawn, execFile, fork } = require('child_process');const child = spawn('node', ['timer.js'], { detached: true, stdio: 'ignore'});child.unref();

當(dāng)調(diào)用子進(jìn)程的unref方法時,同時配置子進(jìn)程的stdio為ignore時,父進(jìn)程就可以獨立退出了

execFile與exec不同,execFile通常用于執(zhí)行文件,而且并不會創(chuàng)建子shell環(huán)境

fork方法是spawn方法的一個特例,fork用于執(zhí)行js文件創(chuàng)建Node.js子進(jìn)程。而且fork方式創(chuàng)建的子進(jìn)程與父進(jìn)程之間建立了IPC通信管道,因此子進(jìn)程和父進(jìn)程之間可以通過send的方式發(fā)送消息。

注意:fork方式創(chuàng)建的子進(jìn)程與父進(jìn)程是完全獨立的,它擁有單獨的內(nèi)存,單獨的V8實例,因此并不推薦創(chuàng)建很多的Node.js子進(jìn)程

fork方式的父子進(jìn)程之間的通信參照下面的例子

parent.js

const { fork } = require('child_process');const forked = fork('child.js');forked.on('message', (msg) => { console.log('Message from child', msg);});forked.send({ hello: 'world' });

child.js

process.on('message', (msg) => { console.log('Message from parent:', msg);});let counter = 0;setInterval(() => { process.send({ counter: counter++ });}, 1000);
node parent.js// 輸出結(jié)果Message from parent: { hello: 'world' }Message from child { counter: 0 }Message from child { counter: 1 }Message from child { counter: 2 }Message from child { counter: 3 }Message from child { counter: 4 }Message from child { counter: 5 }Message from child { counter: 6 }

回到本文初的那個問題,我們就可以將密集計算的邏輯放到單獨的js文件中,然后再通過fork的方式來計算,等計算完成時再通知主進(jìn)程計算結(jié)果,這樣避免主進(jìn)程繁忙的情況了。

compute.js

const longComputation = () => { let sum = 0; for (let i = 0; i < 1e10; i++) { sum += i; }; return sum;};process.on('message', (msg) => { const sum = longComputation(); process.send(sum);});

index.js

const http = require('http');const { fork } = require('child_process');const server = http.createServer();server.on('request', (req, res) => { if (req.url === '/compute') { const compute = fork('compute.js'); compute.send('start'); compute.on('message', sum => {  res.end(`Sum is ${sum}`); }); } else { res.end('Ok') }});server.listen(3000);

監(jiān)聽進(jìn)程事件

通過前述幾種方式創(chuàng)建的子進(jìn)程都實現(xiàn)了EventEmitter,因此可以針對進(jìn)程進(jìn)行事件監(jiān)聽

常用的事件包括幾種:close、exit、error、message

close事件當(dāng)子進(jìn)程的stdio流關(guān)閉的時候才會觸發(fā),并不是子進(jìn)程exit的時候close事件就一定會觸發(fā),因為多個子進(jìn)程可以共用相同的stdio。

close與exit事件的回調(diào)函數(shù)有兩個參數(shù)code和signal,code代碼子進(jìn)程最終的退出碼,如果子進(jìn)程是由于接收到signal信號終止的話,signal會記錄子進(jìn)程接受的signal值。

先看一個正常退出的例子

const { spawn, exec, execFile, fork } = require('child_process');const child = exec('ls -l', { timeout: 300});child.on('exit', function(code, signal) { console.log(code); console.log(signal);});// 輸出結(jié)果0null

再看一個因為接收到signal而終止的例子,應(yīng)用之前的timer文件,使用exec執(zhí)行的時候并指定timeout

const { spawn, exec, execFile, fork } = require('child_process');const child = exec('node timer.js', { timeout: 300});child.on('exit', function(code, signal) { console.log(code); console.log(signal);});// 輸出結(jié)果nullSIGTERM

注意:由于timeout超時的時候error事件并不會觸發(fā),并且當(dāng)error事件觸發(fā)時exit事件并不一定會被觸發(fā)

error事件的觸發(fā)條件有以下幾種:

  • 無法創(chuàng)建進(jìn)程
  • 無法結(jié)束進(jìn)程
  • 給進(jìn)程發(fā)送消息失敗

注意當(dāng)代碼執(zhí)行出錯的時候,error事件并不會觸發(fā),exit事件會觸發(fā),code為非0的異常退出碼

const { spawn, exec, execFile, fork } = require('child_process');const child = exec('ls -l /usrs');child.on('error', function(code, signal) { console.log(code); console.log(signal);});child.on('exit', function(code, signal) { console.log('exit'); console.log(code); console.log(signal);});// 輸出結(jié)果exit1null

message事件適用于父子進(jìn)程之間建立IPC通信管道的時候的信息傳遞,傳遞的過程中會經(jīng)歷序列化與反序列化的步驟,因此最終接收到的并不一定與發(fā)送的數(shù)據(jù)相一致。

sub.js

process.send({ foo: 'bar', baz: NaN });
const cp = require('child_process');const n = cp.fork(`${__dirname}/sub.js`);n.on('message', (m) => { console.log('got message:', m); // got message: { foo: 'bar', baz: null }});

關(guān)于message有一種特殊情況要注意,下面的message并不會被子進(jìn)程接收到

const { fork } = require('child_process');const forked = fork('child.js');forked.send({ cmd: "NODE_foo", hello: 'world'});

當(dāng)發(fā)送的消息里面包含cmd屬性,并且屬性的值是以NODE_開頭的話,這樣的消息是提供給Node.js本身保留使用的,因此并不會發(fā)出message事件,而是會發(fā)出internalMessage事件,開發(fā)者應(yīng)該避免這種類型的消息,并且應(yīng)當(dāng)避免監(jiān)聽internalMessage事件。

message除了發(fā)送字符串、object之外還支持發(fā)送server對象和socket對象,正因為支持socket對象才可以做到多個Node.js進(jìn)程監(jiān)聽相同的端口號。

未完待續(xù)......

參考資料

https://medium.freecodecamp.org/node-js-child-processes-everything-you-need-to-know-e69498fe970a
https://nodejs.org/dist/latest-v10.x/docs/api/child_process.html

總結(jié)

以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,如果有疑問大家可以留言交流,謝謝大家對武林網(wǎng)的支持。

發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 福安市| 无锡市| 松阳县| 方山县| 渭源县| 晋城| 师宗县| 芒康县| 徐闻县| 合江县| 彩票| 遵化市| 清苑县| 台东县| 个旧市| 平乐县| 什邡市| 江源县| 隆林| 新龙县| 唐山市| 平远县| 会同县| 江阴市| 双流县| 娄底市| 昆山市| 香河县| 无为县| 龙游县| 浮山县| 汉阴县| 凤凰县| 江孜县| 新民市| 遂溪县| 耒阳市| 珠海市| 团风县| 集贤县| 杭锦后旗|