进程和线程
- 一个进程可以包含多个线程
- 多核cpu可以启动多个进程去监听服务
- node中主线程是单线程,而且它不支持开启子线程
- 那么当同步任务阻塞,会影响到其他的请求
- 也可以开启子进程,那么需要做一件事,当前的一段逻辑交给子进程处理了,它做完需要告知我(发布订阅)
- 不要滥用进程
阻塞示例
const http = require("http");
http
.createServer((req, res) => {
if (req.url === "/sum") {
let sum = 0;
for (let i = 0; i < 100 * 10000 * 10000; i++) {
sum += i;
}
return res.end("sun:" + sum);
} else {
return res.end("ok");
}
})
.listen(3000);
如何开启子进程
api
spawn
- spawn(执行环境, [执行文件], {...配置})
- 返回一个实例
const path = require("path");
const { spawn } = require("child_process");
const cp = spawn("node", ["index.js"], {
cwd: path.resolve(__dirname),
});
cp.stdout.on("exit", (chunk) => {
console.log('执行结束');
});
cp.stderr.on("close", (chunk) => {
console.log('关闭');
});
options.stdio
- 'pipe' = ['pipe','pipe','pipe']
- 字面上的意思,将输入输出变为流的形式
- 数组中的三个pipe分别代表 stdin、stdout、stderr,使用这三个都可以监听到
- 不过它是将这些内容写到自己的process上,所以需要通过实例(cp)去监听
- 我们可以通过对这三个参数的监听和写入实现通信
process.stdout.write("测试是否接收到");
const cp = spawn("node", ["err.js"], { stdio: "pipe" });
cp.stdout.on("data", (chunk) => {
console.log(chunk.toString());
});
throw 1;
cp.stderr.on("data", (chunk) => {
console.log(`异常:${chunk.toString()}`);
});
process.stdin.on("data", (chunk) => {
console.log("子拿到的啥啊", chunk.toString());
});
console.log(1);
cp.stdin.write("内容");
cp.stdout.on("data", (chunk) => {
console.log("父" + chunk.toString());
});
- 'inherit' = [0,1,2]
- 等同与[process.stdin, process.stdout, process.stderr]
- 这种用法是将子进程与当前进程的输入输出共享,所以也就不需要监听了,子进程的内容会直接再当前命令窗口输出
const cp = spawn("node", ["err.js"], {
stdio: [0, 1, 2],
});
- 使用pipe的通信稍微有一些麻烦,所以可以使用ipc通信
- 在数组末尾添加ipc [0,1,2,'ipc']
- 通信使用send,监听使用message事件
- 传递的不是buffer了,不需要转换字符
- 输入输出共享了
- 不会自动关闭,所以手动kill下
process.on("message", (data) => {
console.log("父pid" + data);
process.kill(process.pid);
});
process.send(process.pid, () => {
console.log("子发送成功");
});
const cp = spawn("node", ["err.js"], {
stdio: [0, 1, 2, "ipc"],
});
cp.on("message", (data) => {
console.log("子进程pid" + data);
cp.send(process.pid, () => {
console.log("父发送成功");
});
});
options.detached
- 默认false
- true为告诉子进程,让其独立执行
- 当为false的时候会与父进程关联,那么父进程挂啦,子进程也就挂了
- 适用于一些定期执行的任务,如定期清理日志
- 需要与stdio:'ignore'和cp.unref()联合使用,否则不生效
spawn("node", ["err.js"], {
stdio: "ignore",
detached: true,
});
cp.unref();
fork
- 是ipc方法的封装,还是基于spawn
- 使用node执行,所以第一个参数可以忽略
- options.stdio就是ipc的参数
...
const cp = fork(["err.js"]);
...
execFile
- 适用于执行某个小文件
- 接收一个回调,回调形参依次是err、stdout、stderr
const { execFile } = require("child_process");
execFile("node", ["cpu.js"], {}, function (err, stdout, stderr) {
console.log(stdout);
});
exec
- 适用于执行一个命令
- 可以理解为将execFile的前两个参数合并,后续参数一致
- execFile有一定的局限性,如获取变量
echo $PATH,在exec中可以直接合并,而在execFile需要分开写,那么拿到的就是个固定字符
execFile("echo", ["$PATH"], {}, function (err, stdout, stderr) {
console.log(stdout);
});
exec("echo $PATH", {}, function (err, stdout, stderr) {
console.log(stdout);
});
适用场景
- 获取大文件pipe
- 小文件或通信 ipc
- 获取固定结果 exec或execFile
解决开始的问题
- 开启多个子进程(最好为主机核数-1),监听同一个端口号
- 当请求过来后,会自动识别当前进程是否被占用,如果占用,就找另外的进程
- 当所有进程都卡住,仍然会卡住,不过已经在一定程度上解决了这个问题
- 想彻底解决的话,还是需要避免这种阻塞
const http = require("http");
const cpus = require("os").cpus();
const { fork } = require("child_process");
const server = http
.createServer((req, res) => {})
.listen(3000);
for (let i = 0, len = cpus.length - 1; i < len; i++) {
let cp = fork("http.js");
cp.send("server", server);
}
const http = require("http");
process.on("message", (type, server) => {
http
.createServer((req, res) => {
console.log(process.pid, "子进程pid");
if (req.url === "/sum") {
let sum = 0;
for (let i = 0; i < 100 * 10000 * 10000; i++) {
sum += i;
}
return res.end("sun:" + sum);
} else {
return res.end("ok");
}
})
.listen(server);
});
使用自带库
- cluster
- node版本要高一点,我用的16
- 首次进入isPrimary为true,代表主进程,调用fork后,再进入就为false,代表子进程
const cluster = require("cluster");
const http = require("http");
const cpus = require("os").cpus();
console.log(cluster.isPrimary);
if (cluster.isPrimary) {
for (let i = 0, len = cpus.length - 1; i < len; i++) {
cluster.fork();
}
} else {
http
.createServer((req, res) => {
console.log(process.pid, "子进程pid");
if (req.url === "/sum") {
let sum = 0;
for (let i = 0; i < 100 * 10000 * 10000; i++) {
sum += i;
}
return res.end("sun:" + sum);
} else {
return res.end("ok");
}
})
.listen(3000, () => {
console.log("创建成功", process.pid);
});
}