js的單線程和多進程

概述

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

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

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

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

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

瀏覽器

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

<!DOCTYPE html>
    <head>
        <title>worker</title>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        <script>
            function init(){
                //創(chuàng)建一個Worker對象,并向它傳遞將在新進程中執(zhí)行的腳本url
                var worker = new Worker('./webworker.js');
                //接收worker傳遞過來的數(shù)據(jù)
                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發(fā)送回主進程
    postMessage(sum);
};
//將執(zhí)行timedCount前的時間,通過postMessage發(fā)送回主進程
postMessage('Before computing, '+new Date());
timedCount();
//結(jié)束timedCount后,將結(jié)束時間發(fā)送回主進程
postMessage('After computing, ' +new Date());

Node

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

進程模型

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

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

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

IPC

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

Node.js 中父進程調(diào)用 fork 產(chǎn)生子進程時,會事先構(gòu)造一個 pipe 用于進程通信。

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

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

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

子進程啟動后通過環(huán)境變量拿到 fd

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

并將 fd 綁定到一個新構(gòu)造的 pipe 上

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

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

總結(jié)下,Nodejs通過pipe實現(xiàn)IPC,主要包括以下幾個步驟:

  • 主進程在fork產(chǎn)生子進程前生成一個pipe占位符,提示后續(xù)會有pipe創(chuàng)建。
  • 通過系統(tǒng)的socketpair把雙工通道綁定到此pipe占位符上。
  • 通過環(huán)境變量把文件描述符fd傳給子進程。
  • 子進程通過fd創(chuàng)建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);
進程失聯(lián)

進程失聯(lián)是在子進程退出前通知主進程,主進程fork一個新的子進程,然后原來的子進程退出;主進程通過是子進程的disconnect事件監(jiān)聽其狀態(tài)。
例子:

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 時我們在談論什么(下)

多進程單線程模型與單進程多線程模型之爭
多進程和多線程的優(yōu)缺點
Node.js的線程和進程

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容