首頁  >  文章  >  web前端  >  node.js中child_process模組和cluster模組的分析(程式碼範例)

node.js中child_process模組和cluster模組的分析(程式碼範例)

不言
不言原創
2018-08-29 11:30:311653瀏覽

這篇文章帶給大家的內容是關於node.js中child_process模組和cluster模組的分析(程式碼範例),有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。

  node遵循的是單執行緒單一進程的模式,node的單執行緒是指js的引擎只有一個實例,且在nodejs的主執行緒中執行,同時node以事件驅動的方式處理IO等非同步操作。 node的單線程模式,只維持一個主線程,大大減少了線程間切換的開銷。

  但是node的單執行緒使得在主執行緒不能進行CPU密集型操作,否則會阻塞主執行緒。對於CPU密集型操作,在node中透過child_process可以建立獨立的子進程,父子進程透過IPC通信,子進程可以是外部應用程式也可以是node子程序,子進程執行後可以將結果傳回給父進程。

  此外,node的單線程,以單一進程運行,因此無法利用多核心CPU以及其他資源,為了調度多核心CPU等資源,node還提供了cluster模組,利用多核心CPU的資源,使得可以通過一串node子程序去處理負載任務,同時確保一定的負載平衡型。本文從node的單線程單一進程的理解觸發,介紹了child_process模組和cluster模組

一、node中的單線程和單進程

  首先要理解的概念是,node的單執行緒和單一行程的模式。 node的單執行緒於其他語言的多執行緒模式相比,減少了執行緒間切換的開銷,以及在寫node程式碼的時候不用考慮鎖定以及執行緒池的問題。 node宣稱的單線程模式,比其他語言更適合IO密集型操作。那麼一個經典的問題是:

node是真的單執行緒的嗎?

提到node,我們就可以立刻想到單一執行緒、非同步IO、事件驅動等字眼。首先要明確的是node真的是單線程的嗎,如果是單線程的,那麼異步IO,以及定時事件(setTimeout、setInterval等)又是在哪裡被執行的。

嚴格來說,node並不是單線程的。 node中存在著多種線程,包括:

  • js引擎執行的線程

  • 定時器線程(setTimeout, setInterval)

  • 異步http線程(ajax)

#.

  我們平時所說的單線程是指node中只有一個js引擎在主執行緒上運行。其他非同步IO和事件驅動相關的執行緒透過libuv來實現內部的執行緒池和執行緒調度。 libv中存在了一個Event Loop,透過Event Loop來切換實作類似多執行緒的效果。簡單的來講Event Loop就是維持一個執行棧和一個事件佇列,當前執行棧中的如果發現異步IO以及定時器等函數,就會把這些非同步回呼函數放入到事件佇列中。目前執行堆疊執行完成後,從事件佇列中,依照一定的順序執行事件佇列中的非同步回呼函數。

node.js中child_process模組和cluster模組的分析(程式碼範例)

上圖中從執行棧,到事件佇列,最後事件佇列中依照一定的順序執行回呼函數,整個過程就是一個簡化版的Event Loop。另外回呼函數執行時,同樣會產生一個執行棧,在回呼函數裡面還有可能巢狀異步的函數,也就是說執行棧存在著巢狀。

也就是說node中的單執行緒是指js引擎只在唯一的主執行緒上運行,其他的非同步操作,也是有獨立的執行緒去執行,透過libv的Event Loop實現了類似於多線程的上下文切換以及線程池調度。執行緒是最小的進程,因此node也是單進程的。這樣就解釋了為什麼node是單執行緒和單一進程的。

二、node中的child_process模組實作多重行程

  node是單一進程的,必然存在一個問題,就是無法充分利用cpu等資源。 node提供了child_process模組來實現子進程,從而實現一個廣義上的多進程的模式。透過child_process模組,可以實現1個主進程,多個子進程的模式,主進程稱為master進程,子進程又稱為工作進程。在子進程中不僅可以呼叫其他node程序,也可以執行非node程序以及shell指令等等,執行完子程序後,以流或回調的形式回傳。

1、child_process模組提供的API

child_process提供了4個方法,用於新建子進程,這4個方法分別為spawn、execFile、exec和fork。所有的方法都是非同步的,可以用一張圖來描述這4個方法的差異。

node.js中child_process模組和cluster模組的分析(程式碼範例)

上圖可以顯示出這4個方法的差別,我們也可以簡單介紹這4個方法的差異。

  • spawn : 子程序中執行的是非node程序,提供一組參數後,執行的結果以流的形式傳回。

  • execFile:子進程中執行的是非node程序,提供一組參數後,執行的結果以回呼的形式傳回。

  • exec:子進程執行的是非node程序,傳入一串shell命令,執行後結果以回調的形式返回,與execFile
    不同的是exec可以直接執行一串shell指令。

  • fork:子進程執行的是node程序,提供一組參數後,執行的結果以流的形式返回,與spawn不同,fork生成的子進程只能執行node應用。接下來的小節將具體的介紹這一些方法。

2、execFile和exec

我們先比較execFile和exec的差別,這兩個方法的相同點:

##執行的是非node應用,且執行後的結果以回呼函數的形式傳回。

不同點是:

exec是直接執行的一段shell指令,而execFile是執行的一個應用程式

舉例來說,echo是UNIX系統的一個自帶命令,我們直接可以在命令列執行:

echo hello world
結果,在命令列中會列印出hello world.

(1) 透過exec來實作

新建一個main.js檔案中,如果要使用exec方法,那麼則在該檔案中寫入:

let cp=require('child_process');
cp.exec('echo hello world',function(err,stdout){
  console.log(stdout);
});
執行這個main. js,結果會輸出hello world。我們發現exec的第一個參數,跟shell指令完全相似。

(2)透過execFile來實作

let cp=require('child_process');
cp.execFile('echo',['hello','world'],function(err,stdout){
   console.log(stdout);
});

execFile類似於執行了名為echo的應用,然後傳入參數。 execFlie會在process.env.PATH的路徑中依序尋找是否有名為'echo'的應用,找到後就會執行。預設的process.env.PATH路徑中包含了'usr/local/bin',而這個'usr/local/bin'目錄中就存在了這個名為'echo'的程序,傳入hello和world兩個參數,執行後返回。

(3)安全分析

像exec那樣,可以直接執行一段shell是極為不安全的,例如有這麼一段shell:

echo hello world;rm -rf
透過exec是可以直接執行的,rm -rf會刪除目前目錄下的檔案。 exec就像命令列一樣,執行的等級很高,執行後會出現安全性的問題,而execFile不同:

execFile('echo',['hello','world',';rm -rf'])
在傳入參數的同時,會偵測傳入實參執行的安全性,如果存在安全性問題,會拋出異常。除了execFile外,spawn和fork也都無法直接執行shell,因此安全性較高。

3、spawn

spawn同樣是用來執行非node應用,且無法直接執行shell,與execFile相比,spawn執行應用後的結果並不是執行完成後,一次性的輸出的,而是以流的形式輸出。對於大批量的資料輸出,透過流的形式可以介紹記憶體的使用。

我們用一個檔案的排序和去重來舉例:

node.js中child_process模組和cluster模組的分析(程式碼範例)

#在上述圖片示意圖中,首先讀取的input. txt檔案中有acba未經排序的文字,經過sort程式後可以實現排序功能,輸出為aabc,最後透過uniq程式可以去重,得到abc。我們可以用spawn流形式的輸入輸出來實現上述功能:

let cp=require('child_process');
let cat=cp.spawn('cat',['input.txt']);
let sort=cp.spawn('sort');
let uniq=cp.spawn('uniq');

cat.stdout.pipe(sort.stdin);
sort.stdout.pipe(uniq.stdin);
uniq.stdout.pipe(process.stdout);
console.log(process.stdout);
執行後,最後的結果會輸入到process.stdout中。如果input.txt這個檔案較大,那麼以流的形式輸入輸出可以明顯減少記憶體的佔用,透過設定緩衝區的形式,減少記憶體佔用的同時也可以提高輸入輸出的效率。

4、fork

在javascript中,在處理大量運算的任務方面,HTML裡面透過web work來實現,使得任務脫離了主執行緒。在node中使用了一種內建於父進程和子進程之間的通訊來處理該問題,降低了大數據運行的壓力。 node中提供了fork方法,透過fork方法在單獨的進程中執行node程序,並且透過父子間的通信,子進程接受父進程的信息,並將執行後的結果返回給父進程。

使用fork方法,可以在父進程和子進程之間開放一個IPC通道,使得不同的node進程間可以進行訊息通訊。

在子進程中:

透過process.on('message')和process.send()的機制來接收和傳送訊息。

在父進程中:

透過child.on('message')和process.send()的機制來接收和傳送消息。

具體例子,在child.js中:

process.on('message',function(msg){
   process.send(msg)
})
在parent.js中:

let cp=require('child_process');
let child=cp.fork('./child');
child.on('message',function(msg){
  console.log('got a message is',msg);
});
child.send('hello world');
執行parent.js會在命令列輸出:got a message is hello world

中斷父子間通訊的方式,可以透過在父行程中呼叫:

child.disconnect()
來實作斷開父子間IPC通訊。

5、同步执行的子进程

exec、execFile、spawn和fork执行的子进程都是默认异步的,子进程的运行不会阻塞主进程。除此之外,child_process模块同样也提供了execFileSync、spawnSync和execSync来实现同步的方式执行子进程。

三、node中的cluster模块

cluster意为集成,集成了两个方面,第一个方面就是集成了child_process.fork方法创建node子进程的方式,第二个方面就是集成了根据多核CPU创建子进程后,自动控制负载均衡的方式。

我们从官网的例子来看:

const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;

if (cluster.isMaster) {
  console.log(`主进程 ${process.pid} 正在运行`);

  // 衍生工作进程。
  for (let i = 0; i  {
    console.log(`工作进程 ${worker.process.pid} 已退出`);
  });
} else {
  // 工作进程可以共享任何 TCP 连接。
  // 在本例子中,共享的是一个 HTTP 服务器。
  http.createServer((req, res) => {
    res.writeHead(200);
    res.end('你好世界\n');
  }).listen(8000);

  console.log(`工作进程 ${process.pid} 已启动`);
}

最后输出的结果为:

$ node server.js
主进程 3596 正在运行
工作进程 4324 已启动
工作进程 4520 已启动
工作进程 6056 已启动
工作进程 5644 已启动

我们将master称为主进程,而worker进程称为工作进程,利用cluster模块,使用node封装好的API、IPC通道和调度机可以非常简单的创建包括一个master进程下HTTP代理服务器 + 多个worker进程多个HTTP应用服务器的架构。

总结

本文首先介绍了node的单线程和单进程模式,接着从单线程的缺陷触发,介绍了node中如何实现子进程的方法,对比了child_process模块中几种不同的子进程生成方案,最后简单介绍了内置的可以实现子进程以及CPU进程负载均衡的内置集成模块cluster。

相关推荐:

node.js中http模块和url模块简介

Node.js中关于多进程模块Cluster的详细介绍以及如何使用

以上是node.js中child_process模組和cluster模組的分析(程式碼範例)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn