node多程序的建立與守護
node是單執行緒執行,我們的node專案如何利用多核CPU的資源,同時提高node服務的穩定性呢?
1. node的單執行緒
程序是一個具有一定獨立功能的程式在一個數據集上的一次動態執行的過程,是作業系統進行資源分配和排程的一個獨立單位,是應用程式執行的載體。
執行緒是程式執行中一個單一的順序控制流,它存在於程序之中,是比程序更小的能獨立執行的基本單位。
早期在單核 CPU 的系統中,為了實現多工的執行,引入了程序的概念,不同的程式執行在資料與指令相互隔離的程序中,通過時間片輪轉排程執行,由於 CPU 時間片切換與執行很快,所以看上去像是在同一時間運行了多個程式。
由於程序切換時需要儲存相關硬體現場、程序控制塊等資訊,所以系統開銷較大。為了進一步提高系統吞吐率,在同一程序執行時更充分的利用 CPU 資源,引入了執行緒的概念。執行緒是作業系統排程執行的最小單位,它們依附於程序中,共享同一程序中的資源,基本不擁有或者只擁有少量系統資源,切換開銷極小。
Node是基於V8引擎之上構建的,決定了他與瀏覽器的機制很類似。
一個node程序只能利用一個核,而且node只能執行在單執行緒中,嚴格意義上,node並非真正的單執行緒架構,即一個程序內可以有多個執行緒,因為node自己還有一定的i/o執行緒存在,這些I/O執行緒由底層的libuv處理,但這些執行緒對node開發者而言是完成透明的,只有在C++擴充套件時才會用到,這裡我們就遮蔽底層的細節,專門討論我們所要關注的。
單執行緒的好處是:程式狀態單一,在沒有多執行緒的情況下,沒有鎖、執行緒同步問題,作業系統在排程時,也因為較少的上下文的切換,可以很好地提高CPU的使用率。然而單核單執行緒也有相應的缺點:
- 這個執行緒掛掉後整個程式就會掛掉;
- 無法充分利用多核資源
2. node多程序的建立
node中有提供child_process
模組,這個模組中,提供了多個方法來建立子程序。
const { spawn, exec, execFile, fork } = require('child_process');
這4個方法都可以建立子程序,不過使用方法還是稍微有點區別。我們以建立一個子程序計算斐波那契數列數列為例,子程序的檔案(worker.js):
// worker.js const fib = (num) => { if (num === 1 || num === 2) { return num; } let a = 1, b = 2, sum = 0; for (let i = 3; i <= num; i++) { sum = a + b; a = b; b = sum; } return sum; } const num = Math.floor(Math.random() * 10) + 3; const result = fib(num); console.log(num, result, process.pid); // process.pid表示當前的程序id
在master.js中如何呼叫這些方法建立子程序呢?
命令 | 使用方法 | 解析 |
---|---|---|
spawn | spawn('node', ['worker.js']) | 啟動一個字程序來執行命令 |
exec | exec('node worker.js', (err, stdout, stderr) => {}) | 啟動一個子程序來執行命令,有回撥 |
execFile | exexFile('worker.js') | 啟動一個子程序來執行可執行的檔案 (頭部要新增#!/usr/bin/env node) |
fork | fork('worker.js') | 與spawn類似,不過這裡只需要自定js檔案模組即可 |
以fork命令為例:
const { fork } = require('child_process');
const cpus = require('os').cpus();
for(let i=0, len=cpus.length; i<len; i++) {
fork('./worker.js');
}
3. 多程序之間的通訊
node中程序的通訊主要在主從(子)程序之間進行通訊,子程序之間無法直接通訊,若要相互通訊,則要通過主程序進行資訊的轉發。
主程序和子程序之間是通過IPC(Inter Process Communication,程序間通訊)進行通訊的,IPC也是由底層的libuv根據不同的作業系統來實現的。
我們還是以計算斐波那契數列數列為例,在這裡,我們用cpu個數減1個的程序來進行計算,剩餘的那一個用來輸出結果。這就需要負責計算的子程序,要把結果傳給主程序,再讓主程序傳給輸出進行,來進行輸出。這裡我們需要3個檔案:
- master.js:用來建立子程序和子程序間的通訊;
- fib.js:計算斐波那契數列;
- log.js:輸出斐波那契數列計算的結果;
主程序:
// master.js
const { fork } = require('child_process');
const cpus = require('os').cpus();
const logWorker = fork('./log.js');
for(let i=0, len=cpus.length-1; i<len; i++) {
const worker = fork('./fib.js');
worker.send(Math.floor(Math.random()*10 + 4)); // 要計算的num
worker.on('message', (data) => { // 計算後返回的結果
logWorker.send(data); // 將結果傳送給輸出程序
})
}
計算程序:
// fib.js
const fib = (num) => {
if (num===1 || num===2) {
return num;
}
let a=1, b=2, sum=0;
for(let i=3; i<num; i++) {
sum = a + b;
a = b;
b = sum;
}
return sum;
}
process.on('message', num => {
const result = fib(num);
process.send(JSON.stringify({
num,
result,
pid: process.pid
}))
})
輸出程序:
process.on('message', data => {
console.log(process.pid, data);
})
當我們執行master時,就能看到各個子程序計算的結果:
第1個數字表示當前輸出子程序的編號,後面表示在各個子程序計算的資料。
同理,我們在進行http服務日誌記錄時,也可以採用類似的思路,多個子程序承擔http服務,剩下的子程序來進行日誌記錄等操作。
當我想用子程序建立伺服器時,採用上面類似斐波那契數列的思路,將fib.js改為httpServer.js:
// httpServer.js
const http = require('http');
http.createServer((req, res) => {
res.writeHead(200, {
'Content-Type': 'text/html'
});
res.end(Math.random()+'');
}).listen(8080);
console.log('http server has started at 8080, pid: '+process.pid);
結果卻出現錯誤了,提示8080埠已經被佔用了:
Error: listen EADDRINUSE: address already in use :::8080
這是因為:在TCP端socket套接字監聽埠有一個檔案描述符,每個程序的檔案描述符都不相同,監聽相同埠時就會失敗。
解決方案有兩種:首先最簡單的就是每個子程序都使用不同的埠,主程序將迴圈的標識給子程序,子程序通過這個標識來使用相關的埠(例如從8080+傳入的標識作為當前程序的埠號)。
第二種方案是,在主程序進行埠的監聽,然後將監聽的套接字傳給子程序。
主程序:
// master.js
const fork = require('child_process').fork;
const net = require('net');
const server = net.createServer();
const child1 = fork('./httpServer1.js'); // random
const child2 = fork('./httpServer2.js'); // now
server.listen(8080, () => {
child1.send('server', server);
child2.send('server', server);
server.close();
})
httpServer1.js:
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, {
'Content-Type': 'text/plain'
});
res.end(Math.random()+', at pid: ' + process.pid);
});
process.on('message', (type, tcp) => {
if (type==='server') {
tcp.on('connection', socket => {
server.emit('connection', socket)
})
}
})
httpServer2.js:
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, {
'Content-Type': 'text/plain'
});
res.end(Date.now()+', at pid: ' + process.pid);
});
process.on('message', (type, tcp) => {
if (type==='server') {
tcp.on('connection', socket => {
server.emit('connection', socket)
})
}
})
我們的2個server,一個是輸出隨機數,一個是輸出當前的時間戳,可以發現這兩個server都可以正常的執行。同時,因為這些程序服務是搶佔式的,哪個程序搶到連線,就哪個程序處理請求。
我們也應當知道的是:
每個程序之間的記憶體資料是不互通的,若我們在某一程序中使用變數快取了資料,另一個程序是讀取不到的。
4. 多程序的守護
剛才我們在第3部分建立的多程序,解決了多核CPU利用率的問題,接下來要解決程序穩定的問題。
每個子程序退出時,都會觸發exit事件
,因此我們通過監聽exit事件來獲知有程序退出了,這時,我們就可以建立一個新的程序來替代。
const fork = require('child_process').fork;
const cpus = require('os').cpus();
const net = require('net');
const server = net.createServer();
const createServer = () => {
const worker = fork('./httpServer.js');
worker.on('exit', () => {
// 當有程序退出時,則建立一個新的程序
console.log('worker exit: ' + worker.pid);
createServer();
});
worker.send('server', server);
console.log('create worker: ' + worker.pid);
}
server.listen(8080, () => {
for(let i=0, len=cpus.length; i<len; i++) {
createServer();
}
})
cluster模組
在多程序守護這塊,node也推出了cluster模組
,用來解決多核CPU的利用率問題。同時cluster中也提供了exit事件來監聽子程序的退出。
一個經典的案例:
const cluster = require('cluster');
const http = require('http');
const cpus = require('os').cpus();
if (cluster.isMaster) {
console.log(`主程序 ${process.pid} 正在執行`);
// 衍生工作程序。
for (let i = 0, len=cpus.length; i < len; i++) {
cluster.fork();
}
cluster.on('exit', (worker) => {
console.log(`工作程序 ${worker.process.pid} 已退出`);
cluster.fork();
});
} else {
http.createServer((req, res) => {
res.writeHead(200);
res.end(Math.random()+ ', at pid: ' + process.pid);
}).listen(8080);
console.log(`工作程序 ${process.pid} 已啟動`);
}
5. 總結
node雖然是單執行緒執行的,但我們可以通過建立多個子程序,來充分利用多核CPU資源,通過可以監聽程序的一些事件,來感知每個程序的執行狀態,來提高我們專案整體的穩定性。
歡迎關注我的公眾號,查閱更多的前端文章:
相關推薦
node多程序的建立與守護
node是單執行緒執行,我們的node專案如何利用多核CPU的資源,同時提高node服務的穩定性呢? 1. node的單執行緒 程序是一個具有一定獨立功能的程式在一個數據集上的一次動態執行的過程,是作業系統進行資源分配和排程的一個獨立單位,是應用程式執行的載體。 執行緒是程式執行中一個單一的順序控制流,它存在
Linux 多工程式設計——多程序建立:fork() 和vfork() 函式詳解
一、fork() 函式詳解 需要的標頭檔案: #include <sys/types.h> #include <unistd.h> pid_t fork(void); 功能: 用於從一個已存在的程序中建立一個新程序,新程序稱為子程序,原程序稱為父程序。
node多程序伺服器
node提供了四種方法來建立子程序,分別是child_process.exec(),child_process.execFile(), child_process.fork(),child_process.spawn()。他們都返回子程序物件。exec:啟動一個子程序執行命令,並且有一個回撥函式獲知子程序的狀
Python多程序原理與實現
1 程序的基本概念 什麼是程序? 程序就是一個程式在一個數據集上的一次動態執行過程。程序一般由程式、資料集、程序控制塊三部分組成。我們編寫的程式用來描述程序要完成哪些功能以及如何完成;資料集則是程式在執行過程中所需要使用的資源;程序控制塊用來記錄程序的外部特徵,描述程序的執行變化
python 多程序併發與多執行緒併發總結
本文對python支援的幾種併發方式進行簡單的總結。 Python支援的併發分為多執行緒併發與多程序併發(非同步IO本文不涉及)。概念上來說,多程序併發即執行多個獨立的程式,優勢在於併發處理的任務都由作業系統管理,不足之處在於程式與各程序之間的通訊和資料共享不
程序建立與fork()的恩怨情仇
一、述說程序: 1、程序(process)是個什麼? 狹義定義:程序是正在執行的程式的例項(an instance of a computer program that is being executed),或者更加簡稱之為“執行中的程式”(但並非一個程
linux fork 多程序建立
1)fork函式將執行著的程式分成2個(幾乎)完全一樣的程序,每個程序都啟動一個從程式碼的同一位置開始執行的執行緒。 這兩個程序中的執行緒繼續執行,就像是兩個使用者同時啟動了該應用程式的兩個副本。 2)fork函式被呼叫一次但返回兩次。兩次返回的唯一區別是子程序中返回0值而父程序中返回子程序ID。
java封裝FFmpeg命令,支援原生ffmpeg全部命令,實現FFmpeg多程序處理與多執行緒輸出控制(開啟、關閉、查詢),rtsp/rtmp推流、拉流
前言: 之前已經對FFmpeg命令進行了封裝http://blog.csdn.net/eguid_1/article/details/51787646,但是當時沒有考慮到擴充套件性,所以總體設計不是太好,需要改動的地方也比較多,也不支援原生ffmpeg命令,所以本次版本推翻
Docker Compose進行多容器建立與日常維護
談到微服務的話題,技術上我們往往會涉及到多服務、多容器的部署與管理。 Docker 有三個主要的作用:Build, Ship和Run。使用docker compose我們可以在Run的層面解決很多實際問題,如:通過建立compose(基於YUML語法)檔案,在這個檔案上面描述應用的架構,如使用什麼映象、資料卷
Windows核心程式設計--程序建立與程序退出
VOID ProcessInherit( VOID ) {STARTUPINFO si = { sizeof(si) };SECURITY_ATTRIBUTES saProcess, saThread;PROCESS_INFORMATION piThreadB, piThreadC;TCHAR szPath
postgresql多例項建立與Slony-I複製配置
第一部分 在一個主機上建立多個 postgresql例項 步驟一:安裝postgresql軟體 安裝 postgresql例項,從postgresql官網上 https://www.postgresql.org/ 下載postgresql安裝軟體,解壓縮,建立po
Linux deamon程序——建立守護程序步驟與setsid()函式
轉載地址 一,守護程序概述 Linux Daemon(守護程序)是執行在後臺的一種特殊程序。它獨立於控制終端並且週期性地執行某種任務或等待處理某些發生的事件。它不需要使用者輸入就能執行而且提供某種服務,不是對整個系統就是對某個使用者程式提供服務。Linux系統的大多數伺服
【nodejs原理&原始碼賞析(4)】深度剖析cluster模組原始碼與node.js多程序(上)
目錄 一. 概述 二. 執行緒與程序 三. cluster模組原始碼解析 3.1 起步 3.2 入口 3.3 主程序模組master.js 3.4 子程序模組c
【nodejs原理&原始碼賞析(6)】深度剖析cluster模組原始碼與node.js多程序(下)
目錄 一. 引言 二.server.listen方法 三.cluster._getServer( )方法 四.跨程序通訊工具方法Utils 五.act:queryServer訊息
最簡單的多線程並發與守護線程與join的運用
sleep all oot rgs -- 時間 ddd 結束 sel import threading import time def run(n): print("talk",n) time.sleep(3) #run("t1") #run("t
node總結之多程序瞭解下
Node.js 是以單執行緒的模式執行的,但它使用的是事件驅動來處理併發,這樣有助於我們在多核 cpu 的系統上建立多個子程序,從而提高效能,其中,每個子程序總是帶有三個流物件:child.stdin, child.stdout 和child.stderr。他們可能會共享父程序的 stdio 流
python 多程序與子程序
多程序: 1.os.fork() 2.from multiprocessing import Process 3.form multiprocessing import Pool 子程序: subprocess 很多時候,子程序並不是自身,而是一個外部程序。我們建立了子程序後,還需要控制
【Linux】多程序與多執行緒之間的區別
http://blog.csdn.net/byrsongqq/article/details/6339240 網路程式設計中設計併發伺服器,使用多程序與多執行緒 ,請問有什麼區別? 答案一: 1,程序:子程序是父程序的複製品。子程序獲得父程序資料空間、堆和棧的複製品。 2,執行緒:相
python 64式: 第14式、多程序,佇列與鎖
#!/usr/bin/env python # -*- coding: utf-8 -*- import multiprocessing import time ''' 關鍵: 1 multiprocessing.Process(group=None, target=None, args=(),
利用multiprocessing建立多程序
import multiprocessing as mp import os import time #將要做的事封裝為函式 def th1(): print(os.getppid(),"----",os.getpid()) print('吃飯早飯') time.sleep(1)