作為一名合格的程序猿/媛双藕,對(duì)于進(jìn)程蠢甲、線程還是有必要了解一點(diǎn)的呻澜,本文將從下面幾個(gè)方向進(jìn)行梳理,盡量做到知其然并知其所以然:
- 進(jìn)程和線程的概念和關(guān)系
- 進(jìn)程演進(jìn)
- 進(jìn)程間通信
- 理解底層基礎(chǔ)涂臣,助力上層應(yīng)用
- 進(jìn)程保護(hù)
進(jìn)程和線程的概念和關(guān)系
用戶下達(dá)運(yùn)行程序的命令后盾计,就會(huì)產(chǎn)生進(jìn)程。同一程序可產(chǎn)生多個(gè)進(jìn)程(一對(duì)多關(guān)系)赁遗,以允許同時(shí)有多位用戶運(yùn)行同一程序闯估,卻不會(huì)相沖突。
進(jìn)程需要一些資源才能完成工作吼和,如CPU使用時(shí)間、存儲(chǔ)器骑素、文件以及I/O設(shè)備炫乓,且為依序逐一進(jìn)行刚夺,也就是每個(gè)CPU核心任何時(shí)間內(nèi)僅能運(yùn)行一項(xiàng)進(jìn)程。
進(jìn)程與線程的區(qū)別:進(jìn)程是計(jì)算機(jī)管理運(yùn)行程序的一種方式末捣,一個(gè)進(jìn)程下可包含一個(gè)或者多個(gè)線程侠姑。線程可以理解為子進(jìn)程。
摘自wiki百科
也就是說箩做,進(jìn)程是我們運(yùn)行的程序代碼和占用的資源總和莽红,線程是進(jìn)程的最小執(zhí)行單位,當(dāng)然也支持并發(fā)邦邦“灿酰可以說是把問題細(xì)化,分成一個(gè)個(gè)更小的問題燃辖,進(jìn)而得以解決鬼店。
并且進(jìn)程內(nèi)的線程是共享進(jìn)程資源的,處于同一地址空間黔龟,所以切換和通信相對(duì)成本小妇智,而進(jìn)程可以理解為沒有公共的包裹容器
。
但是如果進(jìn)程間需要通信的話氏身,也需要一個(gè)公共環(huán)境或者一個(gè)媒介巍棱,這個(gè)就是操作系統(tǒng)。
進(jìn)程演進(jìn)
我們的計(jì)算機(jī)有單核的蛋欣、多核的航徙,也有多種的組合方式:
- 單進(jìn)程
因?yàn)槭且粋€(gè)進(jìn)程,所以某一時(shí)刻只能處理一個(gè)事務(wù)豁状,后續(xù)需要等待捉偏,體驗(yàn)不好
- 多進(jìn)程
為了解決上面的問題,但是如果有很多請(qǐng)求的話泻红,會(huì)產(chǎn)生很多進(jìn)程夭禽,開銷本身就是一個(gè)不小的問題,而進(jìn)程占據(jù)獨(dú)立的內(nèi)存谊路,這么多響應(yīng)是的進(jìn)程難免會(huì)有重復(fù)的狀態(tài)和數(shù)據(jù)讹躯,會(huì)造成資源浪費(fèi)。
- 多進(jìn)程多線程
由之前的進(jìn)程處理事務(wù)缠劝,改成使用線程處理事務(wù)潮梯,解決了開銷大,資源浪費(fèi)的問題惨恭,還可以使用線程池秉馏,預(yù)先創(chuàng)建就緒線程,減少創(chuàng)建和銷毀線程的開銷脱羡。
但是一個(gè)cpu某一時(shí)刻只能處理一個(gè)事務(wù)萝究。像時(shí)間分片來調(diào)度線程的話免都,會(huì)導(dǎo)致線程切換頻繁,是非常耗時(shí)的帆竹。
- 單進(jìn)程單線程
類似也就是v8绕娘,基于事件驅(qū)動(dòng),有效的避免了內(nèi)存開銷和上下文切換栽连,只需要線程間通信险领,即可在適當(dāng)?shù)臅r(shí)刻進(jìn)行事務(wù)結(jié)果等的反饋。
但是遇到計(jì)算量很大的事務(wù)秒紧,會(huì)阻塞后續(xù)任務(wù)的執(zhí)行绢陌。像這樣:
- 單進(jìn)程單線程(多進(jìn)程架構(gòu))
node提供了cluster和child_process兩個(gè)模塊進(jìn)行進(jìn)程的創(chuàng)建,也就是我們常說的主(Master)從(Worker)模式噩茄。Master負(fù)責(zé)任務(wù)調(diào)度和管理Worker進(jìn)程下面,Worker進(jìn)行事務(wù)處理。
進(jìn)程間通信
node本身提供了cluster和child_process模塊創(chuàng)建子進(jìn)程绩聘,本質(zhì)上cluster.fork()是child_process.fork()的上層實(shí)現(xiàn)沥割,cluster帶來的好處是可以監(jiān)聽共享端口,否則建議使用child_process凿菩。
child_process
child_process提供了異步和同步的操作方法机杜,具體可查看文檔。
常見的異步方法有:
除了fork出來的進(jìn)程會(huì)長期駐存外衅谷,其他方式會(huì)在子進(jìn)程任務(wù)完成后以流的方式返回并銷毀進(jìn)程椒拗。
異步方法會(huì)返回ChildProcess的實(shí)例,ChildProcess不能直接創(chuàng)建获黔,只能返回蚀苛。
來看幾張圖吧:
舉個(gè)例子
有一個(gè)很長很長的循環(huán),如果不開啟子進(jìn)程玷氏,會(huì)等循環(huán)之后才能執(zhí)行之后的邏輯堵未。
我們可以將耗時(shí)的循環(huán)放到子進(jìn)程中,主進(jìn)程會(huì)接受子進(jìn)程的返回盏触,不影響后續(xù)事物的處理渗蟹。
// 主進(jìn)程
const execFile = require('child_process').execFile;
execFile('./child.js', [], (err, stdout, stderr) => {
if (err) {
console.log(err);
return;
}
console.log(`stdout: ${stdout}`);
});
console.log('用戶事務(wù)處理');
// 子進(jìn)程
#!/usr/bin/env node
for (let i = 0; i < 10000; i++) {
process.stdout.write(`${i}`);
}
而對(duì)于fork,它是專門用來生產(chǎn)子進(jìn)程的赞辩,也可以說是主進(jìn)程的拷貝雌芽,返回的ChildProcess中會(huì)內(nèi)置額外的通信通道,也就是IPC通道辨嗽,允許消息在父子進(jìn)程間傳遞世落,例如通過文件描述符,不過由于創(chuàng)建的是匿名通道糟需,所以只有主進(jìn)程可以與之通信岛心,其他進(jìn)程無法進(jìn)行通信来破。但相對(duì)的還有命名通道,詳見下一節(jié)忘古。
看一個(gè)簡單的例子:
//parent.js
const cp = require('child_process');
const n = cp.fork(`${__dirname}/sub.js`);
n.on('message', (m) => {
console.log('PARENT got message:', m);
});
n.send({ hello: 'world' });
//sub.js
process.on('message', (m) => {
console.log('CHILD got message:', m);
});
process.send({ foo: 'bar' });
父進(jìn)程通過fork返回的ChildProcess進(jìn)行通信的監(jiān)聽和發(fā)送,子進(jìn)程通過全局變量process進(jìn)行監(jiān)聽和發(fā)送诅诱。
cluster
cluster本質(zhì)上也是通過child_process.fork創(chuàng)建子進(jìn)程髓堪,他還能幫我們合理的管理進(jìn)程。
const cluster = require('cluster');
// 判斷是否為主進(jìn)程
if (cluster.isMaster) {
const cpuNum = require('os').cpus().length;
for (let i = 0; i < cpuNum; ++i) {
cluster.fork();
}
cluster.on('online', (worker) => {
console.log('Create worker-' + worker.process.pid);
});
cluster.on('exit', (worker, code, signal) => {
console.log(
'[Master] worker ' +
worker.process.pid +
' died with code:' +
code +
', and' +
signal
);
cluster.fork(); // 重啟子進(jìn)程
});
} else {
const net = require('net');
net.createServer()
.on('connection', (socket) => {
setTimeout(() => {
socket.end('Request handled by worker-' + process.pid);
}, 10);
})
.listen(8989);
}
細(xì)心地你可能發(fā)現(xiàn)多個(gè)子進(jìn)程監(jiān)聽
了同一個(gè)端口娘荡,這樣不會(huì)EADDRIUNS嗎干旁?
其實(shí)不然,真正監(jiān)聽端口的是主進(jìn)程炮沐,當(dāng)前端請(qǐng)求到達(dá)時(shí)争群,會(huì)將句柄發(fā)送給某個(gè)子進(jìn)程。
理解底層基礎(chǔ)大年,助力上層應(yīng)用
進(jìn)程間通信(IPC)大概有這幾種:
- 匿名管道
- 命名管道
- 信號(hào)量
- 消息隊(duì)列
- 信號(hào)
- 共享內(nèi)存
- 套接字
從技術(shù)上劃分又可以劃分成以下四種:
- 消息傳遞(管道换薄,F(xiàn)IFO,消息隊(duì)列)
- 同步(互斥量翔试,條件變量轻要,讀寫鎖等)
- 共享內(nèi)存(匿名的,命名的)
- 遠(yuǎn)程過程調(diào)用
文件描述符是什么垦缅?
在linux中一切皆文件冲泥,linux會(huì)給每個(gè)文件分配一個(gè)id,這個(gè)id就是文件描述符壁涎,指針也是文件描述符的一種凡恍。這個(gè)很好理解,不過我們可以再往深了說怔球,一個(gè)進(jìn)程啟動(dòng)后嚼酝,會(huì)在內(nèi)核空間(虛擬空間的一部分)創(chuàng)建一個(gè)PCB控制塊,PCB內(nèi)部有一個(gè)文件描述符表庞溜,記錄著當(dāng)前進(jìn)程所有可用的文件描述符(即當(dāng)前進(jìn)程所有打開的文件)革半。系統(tǒng)出了維護(hù)文件描述符表外,還需要維護(hù)打開文件表(Open file table)和i-node表(i-node table)流码。
文件打開表(Open file table)包含文件偏移量又官,狀態(tài)標(biāo)志,i-node表指針等信息
i-node表(i-node table)包括文件類型漫试,文件大小六敬,時(shí)間戳,文件鎖等信息
文件描述符不是一對(duì)一的驾荣,它可以:
- 同一進(jìn)程的不同文件描述符指向同一文件
- 不同進(jìn)程可以擁有相同的文件描述符(比如fork出的子進(jìn)程擁有和父進(jìn)程一樣的文件描述符外构,或者不同進(jìn)程打開同一文件)
- 不同進(jìn)程的同一文件描述符也可以指向不同的文件
- 不同進(jìn)程的不同文件描述符也可以指向同一個(gè)文件
上面提及了很多可以實(shí)現(xiàn)進(jìn)程間通信的方式普泡,那node進(jìn)程間通信是以什么為基礎(chǔ)的呢?
nodeIPC通過管道技術(shù) 加 事件循環(huán)方式進(jìn)行通信审编,管道技術(shù)在windows下由命名管道實(shí)現(xiàn)撼班,在*nix系統(tǒng)則由Unix Domain socket實(shí)現(xiàn),提供給我們的是簡單的message事件和send方法垒酬。
那管道是什么呢砰嘁?
管道實(shí)際上是在內(nèi)核中開辟一塊緩沖區(qū),它有一個(gè)讀端一個(gè)寫端勘究,并傳給用戶程序兩個(gè)文件描述符矮湘,一個(gè)指向讀端,一個(gè)指向?qū)懚丝诳诟猓缓笤摼彺鎱^(qū)存儲(chǔ)不同進(jìn)程間寫入的內(nèi)容缅阳,并供不同進(jìn)程讀取內(nèi)容,進(jìn)而達(dá)到通信的目的景描。
管道又分為匿名管道和命名管道十办,匿名管道常見于一個(gè)進(jìn)程fork出子進(jìn)程,只能親緣進(jìn)程通信伏伯,而命名管道可以讓非親緣進(jìn)程進(jìn)行通信橘洞。
其實(shí)本質(zhì)上來說進(jìn)程間通信是利用內(nèi)核管理一塊內(nèi)存,不同進(jìn)程可以讀寫這塊內(nèi)容说搅,進(jìn)而可以互相通信炸枣,當(dāng)然,說起來簡單弄唧,做起來難适肠。有興趣的朋友可以自行研究。
進(jìn)程保護(hù)
可以用cluster建立主從進(jìn)程架構(gòu)候引,主進(jìn)程調(diào)度管理和分發(fā)任務(wù)給子進(jìn)程侯养,并在子進(jìn)程掛掉或斷開連接后重啟。
pm2是對(duì)cluster的一種封裝澄干,提供了:
- 內(nèi)奸負(fù)載均衡
- 后臺(tái)運(yùn)行
- 停機(jī)重載
- 具有Ubuntu逛揩、CentOS的啟動(dòng)腳本
- 停止不穩(wěn)定的進(jìn)程
- 控制臺(tái)檢測
- 有好的可視化界面
具體原理和細(xì)節(jié)以后有空再做分析。
文中若有錯(cuò)誤的地方麸俘,歡迎指出辩稽,我會(huì)及時(shí)更新。希望讀者借鑒的閱讀从媚。