nodejs(三) --- nodejs進程與子進程


 

  嗯,對於node的學習還遠遠不夠,這里先做一個簡單的api的記錄,后續深入學習。

第一部分:nodejs中的全局對象之process進程對象

  在node中的全局對象是global,相當於瀏覽器中的window,而process進程對象是global的屬性。

  這一部分主要從 process中的事件process中的標准流對象process中的屬性process中的方法這四個方面來介紹,

 

1、process中的事件

  process是EventEmitter的一個實例,所以也具有事件監聽器的特征。 process中的事件監聽器有 exit、 uncaughtException、一些signal。

(1)退出事件: ‘exit’

  exit事件會在進程退出時觸發,用來監聽進程退出的狀態。 在回調函數中會有一個進程退出的狀態碼。 如下: 

process.on('exit', function(code) {
  // 進程退出后,其后的事件循環將會結束,計時器也不會被執行
  setTimeout(function() {
    console.log('This will not run');
  }, 0);
  console.log('進程退出碼是:', code);
});

//進程退出
process.exit();
//進程正常退出,其退出碼為:0

 

(2)未處理異常: ‘uncaughtException’

  當進程異常退出時,會觸發‘uncaughtException’事件,但是這個異常一般並不明確,所以不建議使用。

//異常捕獲
process.on('uncaughtException', function(exception) {
  console.log('捕獲到的異常是:', exception);
});
//一個未定義的方法,用來制造異常
nonexistentFunc();

//輸出
捕獲到的異常是: [ReferenceError: nonexistentFunc is not defined]

 

(3)信號相關事件

  如SIGNIT事件會在使用 ctrl + c的時候觸發次信號:

process.stdin.resume();

//使用Control+C鍵,可以觸發SIGINT信號
process.on('SIGINT', function() {
  console.log('收到SIGINT信號,按Control+D鍵可以退出進程');
});

 

 

2、process中的標准流對象

  在Process中有三個標准流的操作,與stream流操作不同的是,Process中劉操作是阻塞的。

(1)標准輸出流: process.stdout

  這個輸出流對象是一個指向標准輸出流的可寫流 writebal stream。 console.log就是通過 process.stdout 實現的,如下:

console.log = function(d) {
  process.stdout.write(d + '\n');
};

 

 

(2)標准輸入流: process.stdin

  這是一個指向標准輸入流的可讀流 readable stream。 標准輸入流式暫停的,所以必須調用 process.stdin.resume()來恢復 resume 接受。 使用如下:

process.stdin.on('end', function() {
  process.stdout.write('end');
});

//一個讀取輸入流的方法
function gets(cb){
  process.stdin.resume();
  process.stdin.setEncoding('utf8');

  process.stdin.on('data', function(chunk) {
     process.stdin.pause();
     cb(chunk);
  });
}

gets(function(reuslt){
  console.log("["+reuslt+"]");
});

 

 

(3)標准錯誤流:process.stderr

  標准錯誤流是一個可寫流 writable stream。 console.error 就是通過 process.stderr實現的

 

 

3、process中的屬性

(1)進程命令行參數的數組: process.argv

  這是一個當前進程折參數組,第一個參數是node,第二個參數是當前執行的.js文件名,之后是執行的參數列表。

  例如,當前文件名是 process.js,代碼如下:

process.argv.forEach(function(val, index, array) {
  console.log(index + ': ' + val);
});

  那么執行了 node process.js 之后,輸出如下;

0: node
1: /Users/liuht/code/itbilu/demo/process.js

  增加兩個參數 node process.js arg1 arg2執行后,輸出如下:

0: node
1: /Users/liuht/code/itbilu/demo/process.js
2: arg1
3: arg2

 

 

 

(2)啟動進程程序的路徑: process.execPath

   中文意思就是process的執行路徑。 這個屬性會返回啟動進程程序的路徑,例如 node process.js會返回,/user/local/bin/node, 即node的安裝路徑。 process.js 的代碼如下:

console.log(process.execPath);

 

(3)node的命令行參數: process.execArgv

  process.argv不僅僅是命令行參數,還包括其他參數,而這里的process.execArgv就是Node的命令行參數數組。代碼如下:

$ node --harmony script.js --version 

  那么 process.execArgv 返回:

['--harmony'] 

  而 process.argv 返回:

['/usr/local/bin/node', 'script.js', '--version']

 

  

4)node的運行環境對象: process.env

  這個屬性會返回用戶的運行環境對象,如下所示:

 

 

 

(5)進程退出碼:process.exitCode

  這個屬性會返回進程默認的退出碼, 或者process.exit(code)指定的退出碼。

 

(6)node編譯時的版本:process.version

  這個屬性會返回node編譯時的版本號。 

 

(7)node以及其依賴包版本信息: process.versions

  這個屬性返回node以及它所依賴的版本信息,如下:

       

  即node是基於v8的,這里也返回了v8引擎的信息,還有node本身、http_parser、uv、zlib、ares、icu、modules、openssl。

 

(8)node編譯時的配置信息:process.config

  即這個屬性會返回配置信息,與運行./configure腳本生成的config.gypi文件相同。

  

(9)指向啟動腳本的模塊: process.mainModule

  這個屬性會返回指向啟動腳本的模塊,與require.mian類似。 

 

(10)當前的進程ID:process.pid

  返回當前node的進程id。

 

(11)ps中顯示的進程名: process.title

  process.title屬性會返回‘ps’中顯示的進程名。 實際上就是node的路徑。

 

(12)當前CPU的架構: process.arch

  如我的電腦顯示 X64

 

(13)當前進程的運行平台: process.platform 

  該屬性返回執行當前進程的運行平台信息。 如我的電腦返回的是win32。

 

 

4、process中的方法

(1)觸發abort事件: process.abort()

  該方法會使得當前node進程abort。

 

(2)終止當前進程: process.exit([code])

  該方法會終止當前進程,可以接受一個退出狀態的可選參數 code, 不傳入時, 會返回表示成功的狀態碼 0 , 如下:

process.on('exit', function(code) {
  console.log('進程退出碼是:%d', code); // 進程退出碼是:1
});

process.exit(1);

 

(3)獲取/設置進程的GID:process.getgid()、process.setuid(id)

  有些系統不適用,不做講解。   

 

(4)獲取/設置進程的UID:process.getuid()、process.setuid(id)

  不做過多講解。

GID為GroupId,即組ID,用來標識用戶組的唯一標識符
UID為UserId,即用戶ID,用來標識每個用戶的唯一標示符

擴展:
用戶組:將同一類用戶設置為同一個組,如可將所有的系統管理員設置為admin組,便於分配權限,將某些重要的文件設置為所有admin組用戶可以讀寫,這樣可以進行權限分配。
每個用戶都有一個唯一的用戶id,每個用戶組都有一個唯一的組id

 

(5)獲取/設置單錢進程有操作權限的GID數組:process.getgroups()、process.setgroups(groups)

  

(6)初始化group分組訪問列表: process.initgroups(user, extra_group)

  

(7)向指定進程發送一個信息: process.kill(pid[, signal])

  process.kill()方法是用來向指定進程發送一個信號,需要注意的時 kill 方法不僅是用來殺死指定進程的,可以是任何POSIX標准信息。

 

(8)返回內存使用情況:process.memoryUsage()

  該方法用於查看內存使用情況:如下;

{ rss: 23105536, heapTotal: 10522624, heapUsed: 5836208 }

 

(9)延遲方法執行: process.nextTick()  

process.nextTick(callback[, arg][, ...])

  該方法用於延遲回調函數的執行,nextTick方法會將callback中的回調函數延遲到事件循環的下一次循環中,與setTimeout(fn, 0)相比nextTick方法效率高很多,該方法能在任何I/O事件之前調用我們的回調函數:

 

 

(10)設置或者讀取進程文件的權限掩碼: process.umask([mask])

  該方法用於設置或者讀取進程文件的權限掩碼,子進程從父進程中繼承這個掩碼。 如果設定了參數mask那么返回舊的掩碼,否則返回當前的:

var oldmask, newmask = 0022;

oldmask = process.umask(newmask);
console.log('原掩碼: ' + oldmask.toString(8) + '\n'
            '新掩碼: ' + newmask.toString(8));

 

 

(11)返回當前的高精度時間:process.hrtime() 

 

(12)返回node程序已經運行的秒數: process.uptime()

 

 (13)工作目錄切換: process.chdir(directory)、process.cwd()

  process.chdir()用於改變進程的工作目錄。 process.cwd() 方法返回進程當前的工作目錄。 示例如下:

console.log('當前目錄:' + process.cwd());
try {
  process.chdir('/tmp');
  console.log('新目錄:' + process.cwd());
}
catch (err) {
  console.log('chdir: ' + err);
}

//輸出如下
當前目錄:/Users/liuht/code/itbilu/demo
新目錄:/private/tmp

 

 

 

第二部分:nodejs中進程、線程、單線程理解

   這一部分,首先介紹進程和線程,node單線程這些知識的理解,后面介紹如何創建多線程。 

  • 在開啟電腦后,會運行瀏覽器,微信,視頻等軟件,然而cpu數量很少,所以使用的時並發的方式,即cpu給不同的進程分配時間片。
  • 打開視頻,不僅可以有畫面,還有音頻播放等等,其實是這些進程內的線程在起作用。 一個進程至少要有一個線程。

  node和瀏覽器中的JavaScript都是單線程的。 但是,我們要理解node的單線程到底是什么意思?   

實際上, 這里所說的單線程是指我們所編寫的代碼運行在單線程上,實際上node不是真正的單線程。

   

  比如我們執行 node app.js 時啟動了一個進程,但是這個進程並不是只有一個線程,而是同時創建了很多歌線程(比如:異步IO需要的一些IO線程)。 

  但是,仍然只有一個線程會運行我們編寫的代碼。 這就是node中單線程的含義。

  但是node單線程會導致下面的問題:

  • 無法利用多核CPU(只能獲得一個CPU的時間分片)。
  • 錯誤就會引起整個應用退出(整個應用就一個進程,掛了就掛了)。
  • 大量計算長時間占用CPU,導致阻塞線程內的其他操作(異步IO發不出調用,已完成的異步IO回調不能及時執行)。

 

第三部分:nodejs子進程的創建方式

   在node中,大體有三種創建進程的方法

  • exex / execFile
  • spawn
  • fork

exec/execFile

  exec(command, options, callback) 和 execFile(file, args, options, callback) 比較類似,會使用一個 Buffer 來存儲進程執行后的標准輸出結果,他們可以一次性在callback里面獲取到不太適合數據量大的場景。

  另外,exec會首先創建一個新的shell進程出來,然后執行command; execFile則是直接將可執行的file創建為新進程執行。 所以,execfile 會比 exec 高效一些(后者多了一個shell步驟,前者是直接拿到execfile就執行了)。

  exec比較適合來執行 shell 命令, 然后獲取輸出(比如: exec('ps aux | grep "node" ')),但是 execFile 沒有這么實用, 因為它實際上只接受了一個可執行的命令,然后執行(沒法使用shell里面的管道之類的東西)。

  

// child.js
console.log('child argv: ', process.argv);
// parent.js
const child_process = require('child_process');
const p = child_process.exec(
  'node child.js a b', // 執行的命令
  {},
  (err, stdout, stderr) => {
    if (err) {
      // err.code 是進程退出時的 exit code,非 0 都被認為錯誤
      // err.signal 是結束進程時發送給它的信號值
      console.log('err:', err, err.code, err.signal);
    }
    console.log('stdout:', stdout);
    console.log('stderr:', stderr);
  }
);
console.log('child pid:', p.pid);
const p = child_process.execFile(
  'node', // 可執行文件
  ['child.js', 'a', 'b'], // 傳遞給命令的參數
  {},
  (err, stdout, stderr) => {
    if (err) {
      // err.code 是進程退出時的 exit code,非 0 都被認為錯誤
      // err.signal 是結束進程時發送給它的信號值
      console.log('err:', err, err.code, err.signal);
    }
    console.log('stdout:', stdout);
    console.log('stderr:', stderr);
  }
);
console.log('child pid:', p.pid);

兩個方法還可以傳遞一些配置項,如下所示:

{
    // 可以指定命令在哪個目錄執行
    'cwd': null,
    // 傳遞環境變量,node 腳本可以通過 process.env 獲取到         
    'env': {},
    // 指定 stdout 輸出的編碼,默認用 utf8 編碼為字符串(如果指定為 buffer,那 callback 的 stdout 參數將會是 Buffer)       
    'encoding': 'utf8',
    // 指定執行命令的 shell,默認是 /bin/sh(unix) 或者 cmd.exe(windows)
    'shell': '',
    // kill 進程時發送的信號量
    'killSignal': 'SIGTERM',
    // 子進程超時未執行完,向其發送 killSignal 指定的值來 kill 掉進程
    'timeout': 0,
    // stdout、stderr 允許的最大輸出大小(以 byte 為單位),如果超過了,子進程將被 kill 掉(發送 killSignal 值)
    'maxBuffer': 200 * 1024,
    // 指定用戶 id
    'uid': 0,
    // 指定組 id
    'gid': 0
}

 

 

spawn

  spawn(command, args, options)適合用在進程的輸入、輸出數據量比較大的情況(因為它支持steam的方式,而剛才的exec/execFile都是Buffer,而不支持stream的方式), 可以用於任何命令。

// child.js
console.log('child argv: ', process.argv);
process.stdin.pipe(process.stdout);
// parent.js
const p = child_process.spawn(
  'node', // 需要執行的命令
  ['child.js', 'a', 'b'], // 傳遞的參數
  {}
);
console.log('child pid:', p.pid);
p.on('exit', code => {
  console.log('exit:', code);
});

// 父進程的輸入直接 pipe 給子進程(子進程可以通過 process.stdin 拿到)
process.stdin.pipe(p.stdin);

// 子進程的輸出 pipe 給父進程的輸出
p.stdout.pipe(process.stdout);
/* 或者通過監聽 data 事件來獲取結果
var all = '';
p.stdout.on('data', data => {
    all += data; 
});
p.stdout.on('close', code => {
    console.log('close:', code);
    console.log('data:', all);
});
*/

// 子進程的錯誤輸出 pipe 給父進程的錯誤輸出
p.stderr.pipe(process.stderr);

 

我們可以執行 cat bigdata.txt | node parent.js 來進行測試,bigdata.txt 文件的內容將被打印到終端。

spawn 方法的配置(options)如下:

 

{
    // 可以指定命令在哪個目錄執行
    'cwd': null,
    // 傳遞環境變量,node 腳本可以通過 process.env 獲取到         
    'env': {},
    // 配置子進程的 IO
    'stdio': 'pipe',
    // 為子進程獨立運行做好准備
    'detached': false,
    // 指定用戶 id
    'uid': 0,
    // 指定組 id
    'gid': 0
}

 

 

fork

  fork(modulePath, args, options)實際上是spawn的一個“特例”, 會創建一個新的V8實例。新創建的進程只能用來運行node腳本,不能運行其他命令。

// child.js
console.log('child argv: ', process.argv);
process.stdin.pipe(process.stdout);
// parent.js
const p = child_process.fork(
  'child.js', // 需要執行的腳本路徑
  ['a', 'b'], // 傳遞的參數
  {}
);
console.log('child pid:', p.pid);

p.on('exit', code => {
  console.log('exit:', code);
});

 

 

 

總結:

  • exec/execFile: 使用Buffer來存儲進程的輸出,可以在回調函數中獲取輸出結果不太適合數據量大的情況可以執行任何命令; 不創建V8實例。
  • spawn: 支持stream方式操作輸入輸出,適合數據量打的情況; 可以執行任何命令; 不創建v8實例; 可以創建常駐的后台進程。
  • fork: spawn的一個特例; 只能執行node腳本; 會創建一個 V8 實例; 會建立父子進程的IPC通道,能夠進行通信。

 

 

同步/異步

大部分時候,子進程的創建是異步的。也就是說,它不會阻塞當前的事件循環這對於性能的提升很有幫助

當然,有的時候,同步的方式會更方便(阻塞事件循環),比如通過子進程的方式來執行shell腳本時

node同樣提供同步的版本,比如:

  • spawnSync()
  • execSync()
  • execFileSync()

 

 

 

   忽略上面的答案。。

  • 顯然:無法使用child_process.create() 來創建。 
  • spawn無法接受callback作為參數。
  • execFile確實可以直接執行特定程序,參數不被shell解釋,因此更具有安全性。
  • fork可以在父子進程之前建立IPC管道,便於進程間通訊。 
  • 子進程可以是異步的也可以是同步的,大多數時候建立的時異步的,會比較方便。
  • execFile不能執行shell命令,而是直接執行文件。

 

 

 

 

 

 

 

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM