- 百萬(wàn)在線:大型游戲服務(wù)端開(kāi)發(fā)
- 羅培羽
- 2010字
- 2021-09-17 17:04:53
1.5 回頭看操作系統(tǒng)
服務(wù)端編程很注重程序運(yùn)行效率,所以我們要知其然,也要知其所以然。
1.5.1 多進(jìn)程為什么能提升性能
回過(guò)頭想想1.4節(jié)的分布式程序,將多個(gè)程序部署在多臺(tái)物理機(jī)上顯然可以提升性能,那么將它們部署在同一臺(tái)物理機(jī)上是否也能提高性能?
早期的計(jì)算機(jī)只能夠執(zhí)行單個(gè)任務(wù)(如圖1-17所示),程序由代碼段和數(shù)據(jù)段組成,如果計(jì)算機(jī)只需執(zhí)行一個(gè)任務(wù),內(nèi)存的邏輯結(jié)構(gòu)可以很簡(jiǎn)單。圖1-17中內(nèi)存的語(yǔ)句1~5代表代碼段,變量1代表數(shù)據(jù)段,PC代表當(dāng)前程序執(zhí)行到哪條語(yǔ)句了。

圖1-17 單任務(wù)計(jì)算機(jī)的結(jié)構(gòu)示意圖
有些語(yǔ)句會(huì)阻塞程序執(zhí)行,按照1.1.2節(jié)的游戲流程,服務(wù)端需要在玩家登錄時(shí)加載數(shù)據(jù)、登出時(shí)保存數(shù)據(jù)(如代碼1-8所示),由于硬盤(pán)速度很慢,因此類(lèi)似于readFileSync的語(yǔ)句就有可能阻塞程序,因?yàn)橐却x取數(shù)據(jù)后才能再往下執(zhí)行。
代碼1-8 添加數(shù)據(jù)保存功能的服務(wù)端(Node.js)
var server = net.createServer(function(socket){ //新連接//... //斷開(kāi)連接 socket.on('close',function(){ }); });
CPU的執(zhí)行時(shí)序如圖1-18所示,狀態(tài)R代表執(zhí)行中(Runing),狀態(tài)S代表休眠(Sleeping)。CPU只有在R狀態(tài)下才會(huì)忙碌,S狀態(tài)下CPU無(wú)事可做。

圖1-18 代碼1-8中CPU的執(zhí)行時(shí)序
既然CPU可能會(huì)進(jìn)入“無(wú)事可做”的狀態(tài),一種充分利用CPU資源的方法就此產(chǎn)生,即讓物理機(jī)同時(shí)運(yùn)行多個(gè)互不相干的程序(進(jìn)程)。如圖1-19所示,每個(gè)進(jìn)程的代碼段和數(shù)據(jù)段相互獨(dú)立,且它們都會(huì)記錄各自執(zhí)行到哪條語(yǔ)句了(即圖中的PC)。操作系統(tǒng)會(huì)分配CPU,當(dāng)進(jìn)程1無(wú)事可做時(shí),就讓CPU執(zhí)行進(jìn)程2的語(yǔ)句,反之亦然。

圖1-19 多進(jìn)程的內(nèi)存結(jié)構(gòu)
所以,為什么開(kāi)啟多個(gè)程序可以提高執(zhí)行效率?是因?yàn)閱蝹€(gè)程序中可能會(huì)存在一些阻塞語(yǔ)句讓CPU空閑,開(kāi)啟多個(gè)程序可以填補(bǔ)CPU的空閑時(shí)間。
按照以上分析,如果程序中不包含阻塞語(yǔ)句,且運(yùn)行在單核CPU下,同臺(tái)物理機(jī)下部署多個(gè)程序是不能提升性能的。不過(guò)當(dāng)代大多是多核CPU,可以同時(shí)執(zhí)行多個(gè)程序,因此在非阻塞程序中,開(kāi)啟與CPU核心數(shù)相當(dāng)?shù)倪M(jìn)程可以充分利用CPU。在圖1-20中,core1執(zhí)行著進(jìn)程1、core2執(zhí)行著進(jìn)程2、core3和core4無(wú)事可做。

圖1-20 四核CPU示意圖
1.5.2 阻塞為什么不占CPU
除了讀取文件,如果用C、C++、C#開(kāi)發(fā)網(wǎng)絡(luò)程序,還會(huì)用到一些阻塞函數(shù),比如等待客戶端連接的accept函數(shù)、接收數(shù)據(jù)的recv函數(shù)等。那阻塞為什么不會(huì)占用CPU資源呢?
為了便于說(shuō)明,先看看代碼1-9,程序每隔0.1秒打印一句“count is XXX”,代碼中的sleep函數(shù)可以使程序休眠一段時(shí)間。
代碼1-9 阻塞(C語(yǔ)言)
void Block() { int count = 0; while(true) { count++; printf("count is %d", count); sleep(100);//0.1秒 } }
操作系統(tǒng)為了支持多任務(wù),實(shí)現(xiàn)了進(jìn)程調(diào)度的功能,它會(huì)把進(jìn)程分為“運(yùn)行”和“休眠”等幾種狀態(tài)。運(yùn)行狀態(tài)是進(jìn)程獲得CPU使用權(quán),正在執(zhí)行代碼的狀態(tài);休眠狀態(tài)是阻塞狀態(tài),比如代碼1-9運(yùn)行到sleep時(shí),程序會(huì)從運(yùn)行狀態(tài)變?yōu)榈却隣顟B(tài),過(guò)0.1秒后又變回運(yùn)行狀態(tài)。操作系統(tǒng)會(huì)分時(shí)執(zhí)行各個(gè)運(yùn)行狀態(tài)的進(jìn)程,由于速度很快,看上去就像是在同時(shí)執(zhí)行多個(gè)任務(wù)。
圖1-21中的計(jì)算機(jī)運(yùn)行著A、B、C三個(gè)進(jìn)程,其中進(jìn)程A執(zhí)行著代碼1-9中的程序,一開(kāi)始,這三個(gè)進(jìn)程都被操作系統(tǒng)的工作隊(duì)列引用,它們處于運(yùn)行狀態(tài),會(huì)分時(shí)執(zhí)行。

圖1-21 操作系統(tǒng)工作隊(duì)列示意圖
當(dāng)程序執(zhí)行到sleep語(yǔ)句時(shí),操作系統(tǒng)會(huì)將進(jìn)程A從工作隊(duì)列移到等待隊(duì)列中(如圖1-22所示)。這樣一來(lái),工作隊(duì)列中就只剩下進(jìn)程B和進(jìn)程C了。依據(jù)進(jìn)程調(diào)度規(guī)則,CPU會(huì)輪流執(zhí)行這兩個(gè)進(jìn)程的程序,不會(huì)執(zhí)行進(jìn)程A的程序。所以進(jìn)程A被阻塞,不會(huì)往下執(zhí)行代碼,也不會(huì)占用CPU資源。等到條件成立(比如等待一段時(shí)間),操作系統(tǒng)會(huì)重新將進(jìn)程A放入工作隊(duì)列中,繼續(xù)執(zhí)行。

圖1-22 操作系統(tǒng)等待隊(duì)列示意圖
1.5.3 線程會(huì)占用多少資源
1.4節(jié)用“多進(jìn)程”的方案提高了服務(wù)端的承載量,事實(shí)上,使用“多線程”方案亦可。一般的程序(進(jìn)程)包含一個(gè)代碼段和一個(gè)數(shù)據(jù)段,多線程程序則包含了多個(gè)代碼段。如圖1-23所示,進(jìn)程1包含了線程1和線程2這兩個(gè)線程,每個(gè)線程都有它們自己的代碼和PC(記錄運(yùn)行到哪個(gè)語(yǔ)句),它們共享數(shù)據(jù)(變量1)。

圖1-23 多線程程序示意圖
線程會(huì)占用多少資源呢?
·Linux系統(tǒng)默認(rèn)會(huì)給線程分配8MB的棧空間。雖然它承諾給線程8MB的內(nèi)存,但要等到用到時(shí)才會(huì)分配。就像某網(wǎng)盤(pán)標(biāo)榜給每個(gè)用戶2TB大小的空間,實(shí)際并沒(méi)有即刻分配那么多。但占用的實(shí)際內(nèi)存至少會(huì)是一“頁(yè)”,即4KB。
·CPU切換線程需要做很多工作,它執(zhí)行一條語(yǔ)句大概需要幾納秒,完成一次線程切換大概需要幾微秒,花銷(xiāo)較大。開(kāi)啟的線程數(shù)越多,CPU就需要做更多的切換工作,這會(huì)使響應(yīng)變慢。
可見(jiàn),在普通的計(jì)算機(jī)中,雖然操作系統(tǒng)理論上可以支持(近乎)無(wú)限的線程數(shù),但實(shí)際上運(yùn)行幾百個(gè)性能就很不好了(請(qǐng)記住這里的“幾百個(gè)”,后面章節(jié)會(huì)再次提起)。
知識(shí)拓展:1.2.3節(jié)介紹的網(wǎng)絡(luò)模塊的底層實(shí)現(xiàn)有如下兩種方法:
1)每當(dāng)有新的客戶端連接時(shí),開(kāi)啟新線程處理該客戶端。
2)使用多路復(fù)用技術(shù),所謂“多路”,指的是服務(wù)端可以阻塞(如使用epoll_wait)等待多個(gè)客戶端的連接,有任何一個(gè)收到數(shù)據(jù)即返回。
Web服務(wù)器可以用這兩種方法,但游戲服務(wù)端大多只會(huì)用第2種方法。這是因?yàn)閃eb服務(wù)器都是短連接,發(fā)送消息后即斷開(kāi),同時(shí)在線的客戶端很少;游戲服務(wù)端大多是長(zhǎng)連接,同時(shí)在線的玩家很多,方法1只能支持?jǐn)?shù)百名玩家。
- 手機(jī)安全和可信應(yīng)用開(kāi)發(fā)指南:TrustZone與OP-TEE技術(shù)詳解
- Testing with JUnit
- Java面向?qū)ο笏枷肱c程序設(shè)計(jì)
- 微服務(wù)與事件驅(qū)動(dòng)架構(gòu)
- Mastering SVG
- MongoDB for Java Developers
- 青少年美育趣味課堂:XMind思維導(dǎo)圖制作
- INSTANT Django 1.5 Application Development Starter
- Python忍者秘籍
- Android玩家必備
- Java Web開(kāi)發(fā)就該這樣學(xué)
- C++反匯編與逆向分析技術(shù)揭秘(第2版)
- Android開(kāi)發(fā)三劍客:UML、模式與測(cè)試
- MySQL程序員面試筆試寶典
- 零基礎(chǔ)學(xué)Python編程(少兒趣味版)