嗯,對於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命令,而是直接執行文件。