Node特点主线程是单线程的 一个进程只开一个主线程,基于事件驱动的、异步非阻塞I/O,可以应用于高并发场景。
Nodejs中没有多线程,为了充分利用多核cpu,可以使用子进程实现内核的负载均衡,那我们就要解决以下问题:
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路径处理后才能处理/路径
Node.js 进程创建,是通过child_process模块实现的:
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.stdin
,process.stdout
和process.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(); // 放弃控制
衍生新的进程,默认就可以通过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);
通过node
指令,直接执行某个文件:
let childProcess = execFile("node",['./test/sub_process'],function(err,stdout,stdin){
console.log(stdout);
});
内部调用的是 spawn
方法。
let childProcess = exec("node './test/sub_process'",function(err,stdout,stdin){
console.log(stdout)
});
内部调用的是execFile
,其实以上三个方法都是基于spawn
的。
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);
}
紧追技术前沿,深挖专业领域
扫码关注我们吧!