js的單線程和多進程

概述

現行的軟件架構主要有兩種:單進程多線程(如:memcached、redis、mongodb等)和多進程單線程(nginx、node)。
單進程多線程的主要特點:

  • 快:線程比進程輕量,它的切換開銷要少很多。進程相當于函數間切換,每個函數擁有自己的變量;線程相當于一個函數內的子函數切換,它們擁有相同的全局變量。
  • 靈活: 程序邏輯和控制方式簡單,但是鎖和全局變量同步比較麻煩。
  • 穩定性不高: 由于只有一個進程,其內部任何線程出現問題都有可能造成進程掛掉,造成不可用。
  • 性能天花板:線程和主程序受限2G地址空間;當線程到一定數量后,即使增加cpu也不能提升性能。

多進程單線程的主要特點:

  • 高性能:沒有頻繁創建和切換線程的開銷,可以在高并發的情況下保持低內存占用;可以根據CPU的數量增加進程數。
  • 線程安全:沒有必要對變量進行加鎖解鎖的操作
  • 異步非阻塞:通過異步I/O可以讓cpu在I/O等待的時間內去執行其他操作,實現程序運行的非阻塞
  • 性能天花板:進程間的調度開銷大、控制復雜;如果需要跨進程通信,傳輸數據不能太大。

事實上異步通過信號量、消息等方式早就存在操作系統底層,但是一直沒有能在高級語言中推廣使用。
Linux Unix提供了epoll方便了高級語言的異步設計。epoll可以理解為event poll,不同于忙輪詢和無差別輪詢,epoll只會把哪個流發生了怎樣的I/O事件通知我們;libevent和libev都是對epoll的封裝,nginx自己實現了對epoll的封裝。

瀏覽器

在支持html5的瀏覽器里,可以使用webworker來將一些耗時的計算丟入worker進程中執行,這樣主進程就不會阻塞,用戶也就不會有卡頓的感覺了。

<!DOCTYPE html>
    <head>
        <title>worker</title>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        <script>
            function init(){
                //創建一個Worker對象,并向它傳遞將在新進程中執行的腳本url
                var worker = new Worker('./webworker.js');
                //接收worker傳遞過來的數據
                worker.onmessage = function(event){
                    document.getElementById('result').innerHTML+=event.data+"<br/>" ;
                };
            };
        </script>
    </head>
    <body onload = "init()">
        <div id="result"></div>
    </body>
</html>

// webworker.js
var i = 0;
function timedCount(){
    for(var j = 0, sum = 0; j < 100; j++){
        for(var i = 0; i < 100000000; i++){
            sum+=i;
        };
    };
    //將得到的sum發送回主進程
    postMessage(sum);
};
//將執行timedCount前的時間,通過postMessage發送回主進程
postMessage('Before computing, '+new Date());
timedCount();
//結束timedCount后,將結束時間發送回主進程
postMessage('After computing, ' +new Date());

Node

Nodejs通過其內置的cluster模塊實現多進程。cluster是對child_process進行了封裝,目的是發揮多核服務器的性能;pm2 是當下最熱門的帶有負載均衡功能的 Node.js 應用進程管理器。實際開發時,我們不需要關注多進程環境。

進程模型

Node的多進程模型是一個主master多個從worker模式,master的職責如下:

  • 接收外界信號并向各worker進程發送信號
  • 監控woker進程的運行狀態,當woker進程退出后(異常情況下),會自動重新啟動新的woker進程(進程守護)。
盜用圖片..

如果只是簡單的fork幾個進程,多個進程之間會競爭 accpet 一個連接,產生驚群現象,效率比較低。同時由于無法控制一個新的連接由哪個進程來處理,必然導致各 worker 進程之間的負載非常不均衡。

IPC

注: 這部分是從當我們談論 cluster 時我們在談論什么(下)copy而來

Node.js 中父進程調用 fork 產生子進程時,會事先構造一個 pipe 用于進程通信。

  new process.binding('pipe_wrap').Pipe(true);

構造出的 pipe 最初還是關閉的狀態,或者說底層還并沒有創建一個真實的 pipe,直至調用到 libuv 底層的uv_spawn, 利用 socketpair 創建的全雙工通信管道綁定到最初 Node.js 層創建的 pipe 上。
管道此時已經真實的存在了,父進程保留對一端的操作,通過環境變量將管道的另一端文件描述符 fd 傳遞到子進程。

  options.envPairs.push('NODE_CHANNEL_FD=' + ipcFd);

子進程啟動后通過環境變量拿到 fd

  var fd = parseInt(process.env.NODE_CHANNEL_FD, 10);

并將 fd 綁定到一個新構造的 pipe 上

  var p = new Pipe(true);
  p.open(fd);

于是父子進程間用于雙向通信的所有基礎設施都已經準備好了。

總結下,Nodejs通過pipe實現IPC,主要包括以下幾個步驟:

  • 主進程在fork產生子進程前生成一個pipe占位符,提示后續會有pipe創建。
  • 通過系統的socketpair把雙工通道綁定到此pipe占位符上。
  • 通過環境變量把文件描述符fd傳給子進程。
  • 子進程通過fd創建pipe,此pipe替代占位符進行通信。

例子:

// master
const WriteWrap = process.binding('stream_wrap').WriteWrap;
var cp = require('child_process');

var worker = cp.fork(__dirname + '/ipc_worker.js');
var channel = worker._channel;

channel.onread = function (len, buf, handle) {
    if (buf) {
        console.log(buf.toString())
        channel.close()
    } else {
        channel.close()
        console.log('channel closed');
    }
}

var message = { hello: 'worker',  pid: process.pid };
var req = new WriteWrap();
var string = JSON.stringify(message) + '\n';
channel.writeUtf8String(req, string, null);

// worker
const WriteWrap = process.binding('stream_wrap').WriteWrap;
const channel = process._channel;

channel.ref();
channel.onread = function (len, buf, handle) {
    if (buf) {
        console.log(buf.toString())
    }else{
        process._channel.close()
        console.log('channel closed');
    }
}

var message = { hello: 'master',  pid: process.pid };
var req = new WriteWrap();
var string = JSON.stringify(message) + '\n';
channel.writeUtf8String(req, string, null);
進程失聯

進程失聯是在子進程退出前通知主進程,主進程fork一個新的子進程,然后原來的子進程退出;主進程通過是子進程的disconnect事件監聽其狀態。
例子:

const WriteWrap = process.binding('stream_wrap').WriteWrap;
const net = require('net');
const fork = require('child_process').fork;

var workers = [];
for (var i = 0; i < 4; i++) {
     var worker = fork(__dirname + '/multi_worker.js');
     worker.on('disconnect', function () {
         console.log('[%s] worker %s is disconnected', process.pid, worker.pid);
     });
     workers.push(worker);
}

var handle = net._createServerHandle('0.0.0.0', 3000);
handle.listen();
handle.onconnection = function (err,handle) {
    var worker = workers.pop();
    var channel = worker._channel;
    var req = new WriteWrap();
    channel.writeUtf8String(req, 'dispatch handle', handle);
    workers.unshift(worker);
}

const net = require('net');
const WriteWrap = process.binding('stream_wrap').WriteWrap;
const channel = process._channel;
var buf = 'hello Node.js';
var res = ['HTTP/1.1 200 OK','content-length:' + buf.length].join('\r\n') + '\r\n\r\n' + buf;

channel.ref(); //防止進程退出
channel.onread = function (len, buf, handle) {
    console.log('[%s] worker %s got a connection', process.pid, process.pid);
    var socket = new net.Socket({
        handle: handle
    });
    socket.readable = socket.writable = true;
    socket.end(res);
    console.log('[%s] worker %s is going to disconnect', process.pid, process.pid);
    channel.close();
}
參考文章

當我們談論 cluster 時我們在談論什么(上)
當我們談論 cluster 時我們在談論什么(下)

多進程單線程模型與單進程多線程模型之爭
多進程和多線程的優缺點
Node.js的線程和進程

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,527評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,687評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,640評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,957評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,682評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,011評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,009評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,183評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,714評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,435評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,665評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,148評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,838評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,251評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,588評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,379評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,627評論 2 380

推薦閱讀更多精彩內容

  • 第一章 Nginx簡介 Nginx是什么 沒有聽過Nginx?那么一定聽過它的“同行”Apache吧!Ngi...
    JokerW閱讀 32,745評論 24 1,002
  • # 模塊機制 node采用模塊化結構,按照CommonJS規范定義和使用模塊,模塊與文件是一一對應關系,即加載一個...
    RichRand閱讀 2,538評論 0 3
  • https://nodejs.org/api/documentation.html 工具模塊 Assert 測試 ...
    KeKeMars閱讀 6,364評論 0 6
  • NodeJS是單進程單線程 [1] 結構,適合編寫IO密集型的網絡應用。為了充分利用多核CPU的計算能力,最直接的...
    Yonny閱讀 3,611評論 0 7
  • 【墨竹的菜園】0128——今天是冬至,媳婦去外地了,就我和米粒小朋友在家。我們一起和面調餡包餃子。孩子在玩的過程中...
    墨竹的菜園閱讀 546評論 0 0