官术网_书友最值得收藏!

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){
    //新連接
    var data = fs.readFileSync('save.txt');
    //...
    //斷開(kāi)連接
    socket.on('close',function(){
        fs.writeFileSync('save.txt', data)
    });
});

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ù)百名玩家。

主站蜘蛛池模板: 灵川县| 巍山| 石台县| 金川县| 文水县| 高尔夫| 芜湖市| 承德县| 云林县| 宁河县| 定兴县| 体育| 博乐市| 麻阳| 芷江| 红河县| 炎陵县| 海盐县| 巴楚县| 安化县| 枝江市| 宣化县| 东至县| 鹤庆县| 鄂尔多斯市| 通许县| 大宁县| 封丘县| 威信县| 来安县| 甘德县| 梅河口市| 伊宁县| 五峰| 江山市| 德令哈市| 菏泽市| 赤水市| 易门县| 准格尔旗| 黔江区|