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 并发数
多进程与多线程的区别
一般多进程和多线程结合起来使用
评价
-
数据共享与同步
进程间共享数据需要用IPC协议,数据是分开的,同步简单
线程间数据共享比较简单,共享的是进程的数据。但是同步比较复杂 -
内存、cpu
多进程内存占用高,cpu利用率低下
多线程内存占用低,cpu利用率高 -
创建销毁
多进程创建、销毁的开销大,速度慢
多线程在这方面开销小,速度快 -
编程、调试
多进程编程简单、调试简单
多线程编程简单、调试复杂 -
可靠性
多进程比较可靠
多线程,一个线程的崩溃会导致整个进程的崩溃 -
分布式
多进程适用于多核、多机
多线程适用于多核
结论
- 需要频繁创建、销毁的使用多线程 如web服务器,http连接的创建和销毁比较频繁
- 需要大量计算的有限使用多线程
- 需要扩展到多机分布的用进程,多核分布的用线程
- 都满足的条件下,选择最拿手的方式