Node-js 中的進程與線程
1. 回顧進程和線程的定義
-
進程(Process)是計算機中的程序關於某數據集合上的一次運行活動,是系統進行資源分配和調度的基本單位。
-
線程(Thread)是操作系統能夠進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運作單位。
2. Node.js 的單線程
Node 特點主線程是單線程的 一個進程只開一個主線程, 基於事件驅動的、異步非阻塞 I/O,可以應用於高併發場景。
Nodejs 中沒有多線程,爲了充分利用多核 cpu, 可以使用子進程實現內核的負載均衡,那我們就要解決以下問題:
-
Node.js 做耗時的計算時候阻塞問題。
-
Node.js 如何開啓多進程。
-
開發過程中如何實現進程守護。
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路徑處理後才能處理/路徑
4. 開啓進程
Node.js 進程創建,是通過 child_process 模塊實現的:
-
child_process.spawn() 異步生成子進程。
-
child_process.fork() 產生一個新的 Node.js 進程,並使用建立的 IPC 通信通道調用指定的模塊,該通道允許在父級和子級之間發送消息。
-
child_process.exec() 產生一個 shell 並在該 shell 中運行命令。
-
child_process.execFile() 無需產生 shell。
4.1. spawn
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(); // 放棄控制
4.2. fork
衍生新的進程, 默認就可以通過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);
4.3. execFile
通過node
指令,直接執行某個文件:
let childProcess = execFile("node",['./test/sub_process'],function(err,stdout,stdin){
console.log(stdout);
});
內部調用的是 spawn
方法。
4.4. exec
let childProcess = exec("node './test/sub_process'",function(err,stdout,stdin){
console.log(stdout)
});
內部調用的是execFile
,其實以上三個方法都是基於spawn
的。
5. cluster
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);
}
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/sdO66uaM6djKqlI7vcdh8A