進程(Process)是計算機中的程序關於某數據集合上的一次運行活動,是系統進行資源分配和調度的基本單位。
線程(Thread)是作業系統能夠進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運作單位。
2. Node.js的單線程Node特點主線程是單線程的 一個進程只開一個主線程,基於事件驅動的、異步非阻塞I/O,可以應用於高並發場景。
Nodejs中沒有多線程,為了充分利用多核cpu,可以使用子進程實現內核的負載均衡,那我們就要解決以下問題:
3. 場景實例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模塊實現的:
child_process.spawn() 異步生成子進程。
child_process.fork() 產生一個新的Node.js進程,並使用建立的IPC通信通道調用指定的模塊,該通道允許在父級和子級之間發送消息。
child_process.exec() 產生一個shell並在該shell中運行命令。
child_process.execFile() 無需產生shell。
4.1. spawnspawn產卵,可以通過此方法創建一個子進程:
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方法。
4.4. execlet childProcess = exec("node './test/sub_process'",function(err,stdout,stdin){
console.log(stdout)
});
內部調用的是execFile,其實以上三個方法都是基於spawn的。
5. clusterNode.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);
}