摘要:嚴(yán)格來說,并不是單線程的。其他異步和事件驅(qū)動(dòng)相關(guān)的線程通過來實(shí)現(xiàn)內(nèi)部的線程池和線程調(diào)度。線程是最小的進(jìn)程,因此也是單進(jìn)程的。子進(jìn)程中執(zhí)行的是非程序,提供一組參數(shù)后,執(zhí)行的結(jié)果以回調(diào)的形式返回。在子進(jìn)程中通過和的機(jī)制來接收和發(fā)送消息。
??node遵循的是單線程單進(jìn)程的模式,node的單線程是指js的引擎只有一個(gè)實(shí)例,且在nodejs的主線程中執(zhí)行,同時(shí)node以事件驅(qū)動(dòng)的方式處理IO等異步操作。node的單線程模式,只維持一個(gè)主線程,大大減少了線程間切換的開銷。
??但是node的單線程使得在主線程不能進(jìn)行CPU密集型操作,否則會(huì)阻塞主線程。對(duì)于CPU密集型操作,在node中通過child_process可以創(chuàng)建獨(dú)立的子進(jìn)程,父子進(jìn)程通過IPC通信,子進(jìn)程可以是外部應(yīng)用也可以是node子程序,子進(jìn)程執(zhí)行后可以將結(jié)果返回給父進(jìn)程。
??此外,node的單線程,以單一進(jìn)程運(yùn)行,因此無法利用多核CPU以及其他資源,為了調(diào)度多核CPU等資源,node還提供了cluster模塊,利用多核CPU的資源,使得可以通過一串node子進(jìn)程去處理負(fù)載任務(wù),同時(shí)保證一定的負(fù)載均衡型。本文從node的單線程單進(jìn)程的理解觸發(fā),介紹了child_process模塊和cluster模塊,本文的結(jié)構(gòu)安排如下:
node中的單線程和單進(jìn)程
node中的child_process模塊實(shí)現(xiàn)多進(jìn)程
node中的cluster模塊
總結(jié)
原文的地址,在我的博客中:https://github.com/forthealll...
如有幫助,您的star是對(duì)我最好的鼓勵(lì)~
一、node中的單線程和單進(jìn)程??首先要理解的概念是,node的單線程和單進(jìn)程的模式。node的單線程于其他語言的多線程模式相比,減小了線程間切換的開銷,以及在寫node代碼的時(shí)候不用考慮鎖以及線程池的問題。node宣稱的單線程模式,比其他語言更加適合IO密集型操作。那么一個(gè)經(jīng)典的問題是:
node是真的單線程的嗎?
提到node,我們就可以立刻想到單線程、異步IO、事件驅(qū)動(dòng)等字眼。首先要明確的是node真的是單線程的嗎,如果是單線程的,那么異步IO,以及定時(shí)事件(setTimeout、setInterval等)又是在哪里被執(zhí)行的。
嚴(yán)格來說,node并不是單線程的。node中存在著多種線程,包括:
js引擎執(zhí)行的線程
定時(shí)器線程(setTimeout, setInterval)
異步http線程(ajax)
....
??我們平時(shí)所說的單線程是指node中只有一個(gè)js引擎在主線程上運(yùn)行。其他異步IO和事件驅(qū)動(dòng)相關(guān)的線程通過libuv來實(shí)現(xiàn)內(nèi)部的線程池和線程調(diào)度。libv中存在了一個(gè)Event Loop,通過Event Loop來切換實(shí)現(xiàn)類似于多線程的效果。簡單的來講Event Loop就是維持一個(gè)執(zhí)行棧和一個(gè)事件隊(duì)列,當(dāng)前執(zhí)行棧中的如果發(fā)現(xiàn)異步IO以及定時(shí)器等函數(shù),就會(huì)把這些異步回調(diào)函數(shù)放入到事件隊(duì)列中。當(dāng)前執(zhí)行棧執(zhí)行完成后,從事件隊(duì)列中,按照一定的順序執(zhí)行事件隊(duì)列中的異步回調(diào)函數(shù)。
上圖中從執(zhí)行棧,到事件隊(duì)列,最后事件隊(duì)列中按照一定的順序執(zhí)行回調(diào)函數(shù),整個(gè)過程就是一個(gè)簡化版的Event Loop。此外回調(diào)函數(shù)執(zhí)行時(shí),同樣會(huì)生成一個(gè)執(zhí)行棧,在回調(diào)函數(shù)里面還有可能嵌套異步的函數(shù),也就是說執(zhí)行棧存在著嵌套。
也就是說node中的單線程是指js引擎只在唯一的主線程上運(yùn)行,其他的異步操作,也是有獨(dú)立的線程去執(zhí)行,通過libv的Event Loop實(shí)現(xiàn)了類似于多線程的上下文切換以及線程池調(diào)度。線程是最小的進(jìn)程,因此node也是單進(jìn)程的。這樣就解釋了為什么node是單線程和單進(jìn)程的。
二、node中的child_process模塊實(shí)現(xiàn)多進(jìn)程??node是單進(jìn)程的,必然存在一個(gè)問題,就是無法充分利用cpu等資源。node提供了child_process模塊來實(shí)現(xiàn)子進(jìn)程,從而實(shí)現(xiàn)一個(gè)廣義上的多進(jìn)程的模式。通過child_process模塊,可以實(shí)現(xiàn)1個(gè)主進(jìn)程,多個(gè)子進(jìn)程的模式,主進(jìn)程稱為master進(jìn)程,子進(jìn)程又稱工作進(jìn)程。在子進(jìn)程中不僅可以調(diào)用其他node程序,也可以執(zhí)行非node程序以及shell命令等等,執(zhí)行完子進(jìn)程后,以流或者回調(diào)的形式返回。
1、child_process模塊提供的APIchild_process提供了4個(gè)方法,用于新建子進(jìn)程,這4個(gè)方法分別為spawn、execFile、exec和fork。所有的方法都是異步的,可以用一張圖來描述這4個(gè)方法的區(qū)別。
上圖可以展示出這4個(gè)方法的區(qū)別,我們也可以簡要介紹這4中方法的不同。
spawn : 子進(jìn)程中執(zhí)行的是非node程序,提供一組參數(shù)后,執(zhí)行的結(jié)果以流的形式返回。
execFile:子進(jìn)程中執(zhí)行的是非node程序,提供一組參數(shù)后,執(zhí)行的結(jié)果以回調(diào)的形式返回。
exec:子進(jìn)程執(zhí)行的是非node程序,傳入一串shell命令,執(zhí)行后結(jié)果以回調(diào)的形式返回,與execFile
不同的是exec可以直接執(zhí)行一串shell命令。
fork:子進(jìn)程執(zhí)行的是node程序,提供一組參數(shù)后,執(zhí)行的結(jié)果以流的形式返回,與spawn不同,fork生成的子進(jìn)程只能執(zhí)行node應(yīng)用。接下來的小節(jié)將具體的介紹這一些方法。
2、execFile和exec我們首先比較execFile和exec的區(qū)別,這兩個(gè)方法的相同點(diǎn):
執(zhí)行的是非node應(yīng)用,且執(zhí)行后的結(jié)果以回調(diào)函數(shù)的形式返回。
不同點(diǎn)是:
exec是直接執(zhí)行的一段shell命令,而execFile是執(zhí)行的一個(gè)應(yīng)用
舉例來說,echo是UNIX系統(tǒng)的一個(gè)自帶命令,我們直接可以在命令行執(zhí)行:
echo hello world
結(jié)果,在命令行中會(huì)打印出hello world.
(1) 通過exec來實(shí)現(xiàn)新建一個(gè)main.js文件中,如果要使用exec方法,那么則在該文件中寫入:
let cp=require("child_process"); cp.exec("echo hello world",function(err,stdout){ console.log(stdout); });
執(zhí)行這個(gè)main.js,結(jié)果會(huì)輸出hello world。我們發(fā)現(xiàn)exec的第一個(gè)參數(shù),跟shell命令完全相似。
(2)通過execFile來實(shí)現(xiàn)let cp=require("child_process"); cp.execFile("echo",["hello","world"],function(err,stdout){ console.log(stdout); });
execFile類似于執(zhí)行了名為echo的應(yīng)用,然后傳入?yún)?shù)。execFlie會(huì)在process.env.PATH的路徑中依次尋找是否有名為"echo"的應(yīng)用,找到后就會(huì)執(zhí)行。默認(rèn)的process.env.PATH路徑中包含了"usr/local/bin",而這個(gè)"usr/local/bin"目錄中就存在了這個(gè)名為"echo"的程序,傳入hello和world兩個(gè)參數(shù),執(zhí)行后返回。
(3)安全性分析像exec那樣,可以直接執(zhí)行一段shell是極為不安全的,比如有這么一段shell:
echo hello world;rm -rf
通過exec是可以直接執(zhí)行的,rm -rf會(huì)刪除當(dāng)前目錄下的文件。exec正如命令行一樣,執(zhí)行的等級(jí)很高,執(zhí)行后會(huì)出現(xiàn)安全性的問題,而execFile不同:
execFile("echo",["hello","world",";rm -rf"])
在傳入?yún)?shù)的同時(shí),會(huì)檢測傳入實(shí)參執(zhí)行的安全性,如果存在安全性問題,會(huì)拋出異常。除了execFile外,spawn和fork也都不能直接執(zhí)行shell,因此安全性較高。
3、spawnspawn同樣是用于執(zhí)行非node應(yīng)用,且不能直接執(zhí)行shell,與execFile相比,spawn執(zhí)行應(yīng)用后的結(jié)果并不是執(zhí)行完成后,一次性的輸出的,而是以流的形式輸出。對(duì)于大批量的數(shù)據(jù)輸出,通過流的形式可以介紹內(nèi)存的使用。
我們用一個(gè)文件的排序和去重來舉例:
上述圖片示意圖中,首先讀取的input.txt文件中有acba未經(jīng)排序的文字,通過sort程序后可以實(shí)現(xiàn)排序功能,輸出為aabc,最后通過uniq程序可以去重,得到abc。我們可以用spawn流形式的輸入輸出來實(shí)現(xiàn)上述功能:
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);
執(zhí)行后,最后的結(jié)果將輸入到process.stdout中。如果input.txt這個(gè)文件較大,那么以流的形式輸入輸出可以明顯減小內(nèi)存的占用,通過設(shè)置緩沖區(qū)的形式,減小內(nèi)存占用的同時(shí)也可以提高輸入輸出的效率。
4、fork在javascript中,在處理大量計(jì)算的任務(wù)方面,HTML里面通過web work來實(shí)現(xiàn),使得任務(wù)脫離了主線程。在node中使用了一種內(nèi)置于父進(jìn)程和子進(jìn)程之間的通信來處理該問題,降低了大數(shù)據(jù)運(yùn)行的壓力。node中提供了fork方法,通過fork方法在多帶帶的進(jìn)程中執(zhí)行node程序,并且通過父子間的通信,子進(jìn)程接受父進(jìn)程的信息,并將執(zhí)行后的結(jié)果返回給父進(jìn)程。
使用fork方法,可以在父進(jìn)程和子進(jìn)程之間開放一個(gè)IPC通道,使得不同的node進(jìn)程間可以進(jìn)行消息通信。
在子進(jìn)程中:
通過process.on("message")和process.send()的機(jī)制來接收和發(fā)送消息。
在父進(jìn)程中:
通過child.on("message")和process.send()的機(jī)制來接收和發(fā)送消息。
具體例子,在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");
執(zhí)行parent.js會(huì)在命令行輸出:got a message is hello world
中斷父子間通信的方式,可以通過在父進(jìn)程中調(diào)用:
child.disconnect()
來實(shí)現(xiàn)斷開父子間IPC通信。
5、同步執(zhí)行的子進(jìn)程exec、execFile、spawn和fork執(zhí)行的子進(jìn)程都是默認(rèn)異步的,子進(jìn)程的運(yùn)行不會(huì)阻塞主進(jìn)程。除此之外,child_process模塊同樣也提供了execFileSync、spawnSync和execSync來實(shí)現(xiàn)同步的方式執(zhí)行子進(jìn)程。
三、node中的cluster模塊cluster意為集成,集成了兩個(gè)方面,第一個(gè)方面就是集成了child_process.fork方法創(chuàng)建node子進(jìn)程的方式,第二個(gè)方面就是集成了根據(jù)多核CPU創(chuàng)建子進(jìn)程后,自動(dòng)控制負(fù)載均衡的方式。
我們從官網(wǎng)的例子來看:
const cluster = require("cluster"); const http = require("http"); const numCPUs = require("os").cpus().length; if (cluster.isMaster) { console.log(`主進(jìn)程 ${process.pid} 正在運(yùn)行`); // 衍生工作進(jìn)程。 for (let i = 0; i < numCPUs; i++) { cluster.fork(); } cluster.on("exit", (worker, code, signal) => { console.log(`工作進(jìn)程 ${worker.process.pid} 已退出`); }); } else { // 工作進(jìn)程可以共享任何 TCP 連接。 // 在本例子中,共享的是一個(gè) HTTP 服務(wù)器。 http.createServer((req, res) => { res.writeHead(200); res.end("你好世界 "); }).listen(8000); console.log(`工作進(jìn)程 ${process.pid} 已啟動(dòng)`); }
最后輸出的結(jié)果為:
$ node server.js 主進(jìn)程 3596 正在運(yùn)行 工作進(jìn)程 4324 已啟動(dòng) 工作進(jìn)程 4520 已啟動(dòng) 工作進(jìn)程 6056 已啟動(dòng) 工作進(jìn)程 5644 已啟動(dòng)
我們將master稱為主進(jìn)程,而worker進(jìn)程稱為工作進(jìn)程,利用cluster模塊,使用node封裝好的API、IPC通道和調(diào)度機(jī)可以非常簡單的創(chuàng)建包括一個(gè)master進(jìn)程下HTTP代理服務(wù)器 + 多個(gè)worker進(jìn)程多個(gè)HTTP應(yīng)用服務(wù)器的架構(gòu)。
總結(jié)本文首先介紹了node的單線程和單進(jìn)程模式,接著從單線程的缺陷觸發(fā),介紹了node中如何實(shí)現(xiàn)子進(jìn)程的方法,對(duì)比了child_process模塊中幾種不同的子進(jìn)程生成方案,最后簡單介紹了內(nèi)置的可以實(shí)現(xiàn)子進(jìn)程以及CPU進(jìn)程負(fù)載均衡的內(nèi)置集成模塊cluster。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/97086.html
摘要:進(jìn)程間通信的目的是為了讓不同的進(jìn)程能夠互相訪問資源,并進(jìn)程協(xié)調(diào)工作。這個(gè)過程的示意圖如下端口共同監(jiān)聽集群穩(wěn)定之路進(jìn)程事件自動(dòng)重啟負(fù)載均衡狀態(tài)共享模塊工作原理事件二測試單元測試性能測試三產(chǎn)品化項(xiàng)目工程化部署流程性能日志監(jiān)控報(bào)警穩(wěn)定性異構(gòu)共存 內(nèi)容 9.玩轉(zhuǎn)進(jìn)程10.測試11.產(chǎn)品化 一、玩轉(zhuǎn)進(jìn)程 node的單線程只不過是js層面的單線程,是基于V8引擎的單線程,因?yàn)?,V8的緣故,前后...
摘要:在單核系統(tǒng)之上我們采用單進(jìn)程單線程的模式來開發(fā)。由進(jìn)程來管理所有的子進(jìn)程,主進(jìn)程不負(fù)責(zé)具體的任務(wù)處理,主要工作是負(fù)責(zé)調(diào)度和管理。模塊與模塊總結(jié)無論是模塊還是模塊,為了解決實(shí)例單線程運(yùn)行,無法利用多核的問題而出現(xiàn)的。 前言 進(jìn)程與線程是一個(gè)程序員的必知概念,面試經(jīng)常被問及,但是一些文章內(nèi)容只是講講理論知識(shí),可能一些小伙伴并沒有真的理解,在實(shí)際開發(fā)中應(yīng)用也比較少。本篇文章除了介紹概念,通過...
摘要:通過將的給出來的進(jìn)程。恩吞吐率關(guān)于吞吐率有多種解讀,一種是描繪服務(wù)器單位時(shí)間處理請求的能力。而根據(jù)這個(gè)描述的話他的單位就為而這個(gè)指標(biāo)就是上面數(shù)據(jù)中的當(dāng)然,肯定是越大越好了吞吐量這個(gè)和上面的吞吐率很有點(diǎn)關(guān)系的。 首先鄭重聲明:nodeJS 是一門單線程!異步!非阻塞語言!nodeJS 是一門單線程!異步!非阻塞語言!nodeJS 是一門單線程!異步!非阻塞語言! 重要的事情說3遍。 因?yàn)?..
摘要:通過將的給出來的進(jìn)程。恩吞吐率關(guān)于吞吐率有多種解讀,一種是描繪服務(wù)器單位時(shí)間處理請求的能力。而根據(jù)這個(gè)描述的話他的單位就為而這個(gè)指標(biāo)就是上面數(shù)據(jù)中的當(dāng)然,肯定是越大越好了吞吐量這個(gè)和上面的吞吐率很有點(diǎn)關(guān)系的。 首先鄭重聲明:nodeJS 是一門單線程!異步!非阻塞語言!nodeJS 是一門單線程!異步!非阻塞語言!nodeJS 是一門單線程!異步!非阻塞語言! 重要的事情說3遍。 因?yàn)?..
摘要:一旦替換已經(jīng)完成,該模塊將被完全棄用。用作錯(cuò)誤處理事件文件,由在標(biāo)準(zhǔn)功能上的簡單包裝器提供所有模塊都提供這些對(duì)象。 Node.js簡介 Node 定義 Node.js是一個(gè)建立在Chrome v8 引擎上的javascript運(yùn)行時(shí)環(huán)境 Node 特點(diǎn) 異步事件驅(qū)動(dòng) showImg(https://segmentfault.com/img/bVMLD1?w=600&h=237); no...
閱讀 2665·2021-09-09 09:33
閱讀 2820·2019-08-30 15:54
閱讀 2878·2019-08-30 14:21
閱讀 2365·2019-08-29 17:15
閱讀 3589·2019-08-29 16:13
閱讀 2769·2019-08-29 14:21
閱讀 3434·2019-08-26 13:25
閱讀 2036·2019-08-26 12:14