一次面试中,我提到自己用过pm2,面试接着问:「那你知道pm2父子进程通信方式吗」。我大概听说pm2有cluster模式,但不清楚父子进程如何通信。面试结束后把NodeJS的多进程重新整理了一下。
对于前端开发同学,一定很清楚js是单线程非阻塞的,这决定了NodeJS能够支持高性能的服务的开发。 JavaScript的单线程非阻塞特性让NodeJS适合IO密集型应用,因为JavaScript在访问磁盘/数据库/RPC等时候不需要阻塞等待结果,而是可以异步监听结果,同时继续向下执行。
但js不适合计算密集型应用,因为当JavaScript遇到耗费计算性能的任务时候,单线程的缺点就暴露出来了。后面的任务都要被阻塞,直到耗时任务执行完毕。
为了优化NodeJS不适合计算密集型任务的问题,NodeJS提供了多线程和多进程的支持。
多进程和多线程从两个方面对计算密集型任务进行了优化,异步和并发:
看下面例子,这是一个koa接口,里面有耗时任务,会阻塞其他任务执行。
const Koa = require('koa');
const app = new Koa();
app.use(async ctx => {
const url = ctx.request.url;
if (url === '/') {
ctx.body = 'hello';
}
if (url === '/compute') {
let sum = 0;
for (let i = 0; i < 1e20; i++) {
sum += i;
}
ctx.body = `${
sum}`;
}
});
app.listen(3000, () => {
console.log('http://localhost:300/ start')
});
可以通过多线程和多进程来解决这个问题。
NodeJS提供多线程模块worker_threads
,其中Woker
模块用来创建线程,parentPort
用在子线程中,可以获取主线程引用,子线程通过parentPort.postMessage
发送数据给主线程,主线程通过worker.on
接受数据。
//api.js
const Koa = require('koa');
const app = new Koa();
const {
Worker} = require('worker_threads');
app.use(async (ctx) => {
const url = ctx.request.url;
if (url === '/') {
ctx.body = 'hello';
}
if (url === '/compute') {
const sum = await new Promise(resolve => {
const worker = new Worker(__dirname + '/compute.js');
//接收信息
worker.on('message', data => {
resolve(data);
})
});
ctx.body = `${
sum}`;
}
})
app.listen(3000, () => {
console.log('http://localhost:3000/ start')
});
//computer.js
const {
parentPort} = require('worker_threads')
let sum = 0;
for (let i = 0; i < 1e20; i++) {
sum += i;
}
//发送信息
parentPort.postMessage(sum);
下面是使用多进程解决耗时任务的方法,多进程模块child_process
提供了fork
方法(后面会介绍更多创建子进程的方法),可以用来创建子进程,主进程通过fork返回值(worker
)持有子进程的引用,并通过worker.on
监听子进程发送的数据,子进程通过process.send
给父进程发送数据。
//api.js
const Koa = require('koa');
const app = new Koa();
const {
fork} = require('child_process');
app.use(async ctx => {
const url = ctx.request.url;
if (url === '/') {
ctx.body =