Node-js 中的進程與線程

1. 回顧進程和線程的定義

2. Node.js 的單線程

Node 特點主線程是單線程的 一個進程只開一個主線程, 基於事件驅動的、異步非阻塞 I/O,可以應用於高併發場景。

Nodejs 中沒有多線程,爲了充分利用多核 cpu, 可以使用子進程實現內核的負載均衡,那我們就要解決以下問題:

3. 場景實例

const http = require('http');
http.createServer((req,res)=>{
    if(req.url === '/sum'){ // 求和
        let sum = 0;
        for(let i = 0 ; i < 10000000000 ;i++){
            sum+=i;
        }
        res.end(sum+'')
    }else{
        res.end('end');
    }
}).listen(3000);
// 這裏我們先訪問/sum,在新建一個瀏覽器頁卡訪問/ 
// 會發現要等待/sum路徑處理後才能處理/路徑

4. 開啓進程

Node.js 進程創建,是通過 child_process 模塊實現的:

4.1. spawn

spawn產卵,可以通過此方法創建一個子進程:

let { spawn } = require("child_process");
let path = require("path");
// 通過node命令執行sub_process.js文件
let childProcess = spawn("node",['sub_process.js']{
  cwd: path.resolve(__dirname, "test"), // 找文件的目錄是test目錄下
  stdio: [0, 1, 2] 
});
// 監控錯誤
childProcess.on("error"function(err) {
  console.log(err);
});
// 監聽關閉事件
childProcess.on("close"function() {
  console.log("close");
});
// 監聽退出事件
childProcess.on("exit"function() {
  console.log("exit");
});

stido這個屬性非常有特色,這裏我們給了 0,1,2 這三個值分別對應住進程的process.stdinprocess.stdoutprocess.stderr這代表着主進程和子進程共享標準輸入和輸出:

let childProcess = spawn("node",['sub_process.js']{
  cwd: path.resolve(__dirname, "test"), // 找文件的目錄是test目錄下
  stdio: [0, 1, 2] 
});

可以在當前進程下打印sub_process.js執行結果默認在不提供 stdio 參數時爲stdio:['pipe'],也就是隻能通過流的方式實現進程之間的通信:

let { spawn } = require("child_process");
let path = require("path");
// 通過node命令執行sub_process.js文件
let childProcess = spawn("node",['sub_process.js']{
  cwd: path.resolve(__dirname, "test"),
  stdio:['pipe'] // 通過流的方式
});
// 子進程讀取寫入的數據
childProcess.stdout.on('data',function(data){
    console.log(data);
});
// 子進程像標準輸出中寫入
process.stdout.write('hello');

使用ipc方式通信,設置值爲stdio:['pipe','pipe','pipe','ipc']可以通過on('message')send方式進行通信:

let { spawn } = require("child_process");
let path = require("path");
// 通過node命令執行sub_process.js文件
let childProcess = spawn("node",['sub_process.js']{
  cwd: path.resolve(__dirname, "test"),
  stdio:['pipe','pipe','pipe','ipc'] // 通過流的方式
});
// 監聽消息
childProcess.on('message',function(data){
    console.log(data);
});
// 發送消息
process.send('hello');

還可以傳入ignore進行忽略,傳入inherit表示默認共享父進程的標準輸入和輸出。

產生獨立進程:

let { spawn } = require("child_process");
let path = require("path");
// 通過node命令執行sub_process.js文件
let child = spawn('node',['sub_process.js'],{
    cwd:path.resolve(__dirname,'test'),
    stdio: 'ignore',
    detached:true // 獨立的線程
});
child.unref(); // 放棄控制

4.2. fork

衍生新的進程, 默認就可以通過ipc方式進行通信:

let { fork } = require("child_process");
let path = require("path");
// 通過node命令執行sub_process.js文件
let childProcess = fork('sub_process.js'{
  cwd: path.resolve(__dirname, "test"),
});
childProcess.on('message',function(data){
    console.log(data);
});

fork是基於spawn的,可以多傳入一個silent屬性來設置是否共享輸入和輸出。

fork 原理:

function fork(filename,options){
    let stdio = ['inherit','inherit','inherit']
    if(options.silent){ // 如果是安靜的  就忽略子進程的輸入和輸出
        stdio = ['ignore','ignore','ignore']
    }
    stdio.push('ipc'); // 默認支持ipc的方式
    options.stdio = stdio
    return spawn('node',[filename],options)
}

到了這裏我們就可以解決 “3. 場景實例” 中的場景實例了:

const http = require('http');
const {fork} = require('child_process');
const path = require('path');
http.createServer((req,res)=>{
    if(req.url === '/sum'){
        let childProcess = fork('calc.js',{
            cwd:path.resolve(__dirname,'test')
        });
        childProcess.on('message',function(data){
            res.end(data+'');
        })
    }else{
        res.end('ok');
    }
}).listen(3000);

4.3. execFile

通過node指令,直接執行某個文件:

let childProcess = execFile("node",['./test/sub_process'],function(err,stdout,stdin){
    console.log(stdout); 
});

內部調用的是 spawn方法。

4.4. exec

let childProcess = exec("node './test/sub_process'",function(err,stdout,stdin){
    console.log(stdout)
});

內部調用的是execFile,其實以上三個方法都是基於spawn的。

5. cluster

Node.js 的單個實例在單個線程中運行。爲了利用多核系統,用戶有時會希望啓動 Node.js 進程集羣來處理負載。自己通過進程來實現集羣。

子進程與父進程共享 HTTP 服務器 fork 實現:

let http = require('http');
let {
    fork
} = require('child_process');
let fs = require('fs');
let net = require('net');
let path = require('path');
let child = fork(path.join(__dirname, '8.child.js'));
let server = net.createServer();
server.listen(8080, '127.0.0.1'function () {
    child.send('server', server);
    console.log('父進程中的服務器已經創建');
    let httpServer = http.createServer();
    httpServer.on('request'function (req, res) {
        if (req.url != '/favicon.ico') {
            let sum = 0;
            for (let i = 0; i < 100000; i++) {
                sum += 1;
            }
            res.write('客戶端請求在父進程中被處理。');
            res.end('sum=' + sum);
        }
    });
    httpServer.listen(server);
});
let http = require('http');
process.on('message'function (msg, server) {
    if (msg == 'server') {
        console.log('子進程中的服務器已經被創建');
        let httpServer = http.createServer();
        httpServer.on('request'function (req, res) {
            if (req.url != '/favicon.ico') {
                sum = 0;
                for (let i = 0; i < 10000; i++) {
                    sum += i;
                }
                res.write('客戶端請求在子進程中被處理');
                res.end('sum=' + sum);
            }
        });
        httpServer.listen(server);
    }
});

進程與父進程共享 socket 對象:

let {
    fork
} = require('child_process');
let path = require('path');
let child = fork(path.join(__dirname, '11.socket.js'));
let server = require('net').createServer();
server.on('connection'function (socket) {
    if (Date.now() % 2 == 0) {
        child.send('socket', socket);
    } else {
        socket.end('客戶端請求被父進程處理!');
    }
});
server.listen(41234, );
process.on('message'function (m, socket) {
    if (m === 'socket') {
        socket.end('客戶端請求被子進程處理.');
    }
});

使用 cluster 模塊更加方便:

let cluster = require("cluster");
let http = require("http");
let cpus = require("os").cpus().length;
const workers = {};
if (cluster.isMaster) {
    cluster.on('exit',function(worker){
        console.log(worker.process.pid,'death')
        let w = cluster.fork();
        workers[w.pid] = w;
    })
  for (let i = 0; i < cpus; i++) {
    let worker = cluster.fork();
    workers[worker.pid] = worker;
  }
} else {
  http
    .createServer((req, res) ={
      res.end(process.pid+'','pid');
    })
    .listen(3000);
  console.log("server start",process.pid);
}
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/sdO66uaM6djKqlI7vcdh8A