Nodejs 多进程与多线程

  • 为什么要使用多进程
  • 多进程与多线程介绍
  • Nodejs多进程和多线程 使用与区别
  • cluster

为什么需要多进程

nodejs单线程,在处理http请求的时候一个错误都会导致进程退出,这是灾难级的

进程和线程介绍

进程是资源分配的最小单位,线程是cpu调度的最小单元
进程有独立的地址空间,线程是进程的一个执行流,一个不同的执行路径,一个进程由多个线程组成,线程有自己的独立堆栈,但是没有自己独立的地址空间,一个线程的崩溃会导致整个进程的崩溃

多线程与多进程的创建与使用

多进程

child_process
exec: 异步执行shell, 然后在shell中执行命令,缓冲任何产生的输出,运行结束后调用回调函数
execSync: exec的同步版本
execFile: exec执行特定的shell文件,不被bash解释,安全性较高
spawn: 创建一个子进程来执行特定的shell, 用法与exec类似,属于异步执行,只能通过监听事件来获取运行结果,适用子进程长时间运行的情况
fork: 直接创建一个子进程,执行Node脚本,与spawn不同的是,fork会在父子之间建立一个通信管道,属于进程之间的通信

// main 
const child = child_process.fork(filePath)
child.on('message',m=>console.log('主线程收到消息'))
child.send({hello:'hello'})


//child 
process.on('message',m=>console.log('子线程收到消息',m))
process.send({foo:'bar'})

cluster

cluster实现了父子进程通信、负载均衡、子进程管理等问题

  • isMaster
  • isWorker
  • fork
import cluster from 'cluster'
import os from 'os'
import http from 'http'
const cpus = os.cpus().length


// cluster基本原理: 主线程去fork子线程,然后管理他们
if (cluster.isMaster) {
    // 主线程
    // cluster.fork()
    Array.from({length:cpus}).forEach(v=>{
        // 开启子进程
        cluster.fork()
        cluster.on('exit',(worker,code,signal)=>{
            console.log('process '+worker.process.pid + 'died');
        })
    })
} else {
    // 子线程
    http.createServer((req, res) => {
        res.end('hello')
    }).listen(8080, () => {
        console.log('server run at 8080');
    })
}

多线程

work_threads模块
2018年 Node 10.5以后才支持了多线程

  • Worker, 主线程用于创建 worker 线程的对象类型,包含 MessagePort 操作以及一些特有的子线程元数据( meta data)
  • isMainThread, false 标识当前代码作为子线程运行,true 则表示主线程
  • parentPort, 在 worker 线程里表示父进程的 MessagePort 类型的对象,在主线里为 null,这个 parentPort 是用于父子通信的;
  • workerData, 在 worker 线程里是父进程创建 worker 线程实例时初始化的数据,在主线程中为 undefined;
  • threadId, 当前 worker 线程的线程 ID,在父进程里是 0;
  • MessageChannel, 包含一对儿能够互相跨线程通信的 MessagePort 类型的对象,可用于创建自定义的通信频道,用于线程间通信;
  • MessagePort, 用于线程间通信的句柄,继承自 EventEmitter 类,有 close 等事件;
if (isMainThread) {
    const worker = new Worker(__filename);

    worker.postMessage({name: 'ego同学'});
    worker.once('message', (message) => {
        console.log('主线程接收信息:', message);
    });
} else {
    parentPort.once('message', (obj) => {
        console.log('子线程接收信息:', obj);
        parentPort.postMessage(obj.name);
    })
}
//main.js
const path = require('path');

const { port1, port2 } = new MessageChannel();
if (isMainThread) {
    const worker1 = new Worker(__filename);
    const worker2 = new Worker(path.join(__dirname, 'worker.js'));

    worker1.postMessage({ port1 }, [ port1 ]);
    worker2.postMessage({ port2 }, [ port2 ]);
} else {
	parentPort.once('message', ({ port1 }) => {
        console.log('子线程1收到port1', port1);
        port1.once('message', (msg) => {
            console.log('子线程1收到', msg);
        })

        port1.postMessage('port1 向 port2 发消息啦');
    })
}

// worker.js
const { parentPort } = require('worker_threads');

parentPort.once('message', ({ port2 }) => {
    console.log('子线程2收到port2');

    port2.once('message', (msg) => {
        console.log('子线程2收到', msg);
    })

    port2.postMessage('这里是port2, over!');
})

压测

使用ab, ab是apache自带的压测工具
ab -n1000 -c20 'host:8080/'

n 请求数量
c 并发数

多进程与多线程的区别

一般多进程和多线程结合起来使用

评价

  1. 数据共享与同步
    进程间共享数据需要用IPC协议,数据是分开的,同步简单
    线程间数据共享比较简单,共享的是进程的数据。但是同步比较复杂

  2. 内存、cpu
    多进程内存占用高,cpu利用率低下
    多线程内存占用低,cpu利用率高

  3. 创建销毁
    多进程创建、销毁的开销大,速度慢
    多线程在这方面开销小,速度快

  4. 编程、调试
    多进程编程简单、调试简单
    多线程编程简单、调试复杂

  5. 可靠性
    多进程比较可靠
    多线程,一个线程的崩溃会导致整个进程的崩溃

  6. 分布式
    多进程适用于多核、多机
    多线程适用于多核


结论

  • 需要频繁创建、销毁的使用多线程 如web服务器,http连接的创建和销毁比较频繁
  • 需要大量计算的有限使用多线程
  • 需要扩展到多机分布的用进程,多核分布的用线程
  • 都满足的条件下,选择最拿手的方式
posted @ 2022-08-17 15:38  IslandZzzz  阅读(808)  评论(0编辑  收藏  举报