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命令,而是直接執行文件。
?
?
?
?
?
?
?
轉載于:https://www.cnblogs.com/zhuzhenwei918/p/7422016.html
總結
以上是生活随笔為你收集整理的nodejs(三) --- nodejs进程与子进程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python作业,要求简单点,急!!!
- 下一篇: 0. VS2015快捷键