談高并發(fā)服務(wù)模型選擇之前,我們先來(lái)看下程序的的任務(wù)類(lèi)型,程序任務(wù)類(lèi)型一般分為 CPU 密集型任務(wù)和 IO 密集型任務(wù),這兩種任務(wù)有各自的特點(diǎn),對(duì)程序的要求是不一樣的需要分開(kāi)對(duì)待。
CPU密集型任務(wù)
一個(gè)程序任務(wù)大部分是計(jì)算類(lèi)的,比如邏輯處理、數(shù)值比較和計(jì)算,我們就稱(chēng)它是 CPU 密集型任務(wù)或計(jì)算密集型任務(wù)。CPU 密集型任務(wù)的特點(diǎn)是要進(jìn)行大量的計(jì)算,消耗 CPU 資源,比如計(jì)算圓周率、視頻編解碼這些靠的是 CPU 的運(yùn)算能力。
CPU 密集型任務(wù)雖然也可以用多任務(wù)完成,但是任務(wù)越多,任務(wù)之間切換的時(shí)間就越多,CPU 執(zhí)行效率反而更低,所以要最高效地利用 CPU,任務(wù)并行數(shù)應(yīng)當(dāng)?shù)扔?CPU 的核心數(shù),避免任務(wù)在 CPU 核之間頻繁切換。
芯片線路 |圖片來(lái)源:www.hippopx.com License CC0
IO密集型任務(wù)
一個(gè)程序涉及到大量網(wǎng)絡(luò)、磁盤(pán)等比較耗時(shí)的輸入輸出任務(wù),就稱(chēng)它是 IO 密集型任務(wù),這類(lèi)任務(wù)的特點(diǎn)是 CPU 消耗很少,任務(wù)的大部分時(shí)間都在等待 IO 操作完成(因?yàn)?IO 的速度遠(yuǎn)遠(yuǎn)低于 CPU 和內(nèi)存的速度,不是一個(gè)數(shù)量級(jí)的)。
對(duì)于 IO 密集型任務(wù),任務(wù)越多 CPU 效率越高,但也不是無(wú)限的開(kāi)啟多任務(wù),如果任務(wù)過(guò)多頻繁切換的開(kāi)銷(xiāo)也不可忽視。常見(jiàn)的大部分程序都是執(zhí)行 IO 密集型任務(wù),比如互聯(lián)網(wǎng)業(yè)務(wù)的 Web 服務(wù),數(shù)據(jù)庫(kù)操作等。
答案是沒(méi)有最好,服務(wù)模型選擇要結(jié)合自身服務(wù)處理的任務(wù)類(lèi)型。任務(wù)類(lèi)型就是我們上面說(shuō)的 CPU 密集型和 IO 密集型,只有清楚的知道所處理業(yè)務(wù)的任務(wù)類(lèi)型,才能在上述服務(wù)模型中選擇其一或多種模型組合,來(lái)搭建適合你的高性能服務(wù)框架。
條件變量是用來(lái)等待而不是用來(lái)上鎖的。條件變量用來(lái)自動(dòng)阻塞一個(gè)線程,直到某特殊情況發(fā)生為止。適合多個(gè)線程等待某個(gè)條件的發(fā)生,不使用條件變量,那么每個(gè)線程就不斷嘗試互斥鎖并檢測(cè)條件是否發(fā)生,浪費(fèi)系統(tǒng)資源。通常條件變量和互斥鎖同時(shí)使用。條件的檢測(cè)是在互斥鎖的保護(hù)下進(jìn)行的。如果一個(gè)條件為假,一個(gè)線程自動(dòng)阻塞,并釋放等待狀態(tài)改變的互斥鎖。如果另一個(gè)線程改變了條件,它發(fā)信號(hào)給關(guān)聯(lián)的條件變量,喚醒一個(gè)或多個(gè)等待它的線程,重新獲得互斥鎖,重新評(píng)價(jià)條件,可以用來(lái)實(shí)現(xiàn)線程間的同步。條件變量系統(tǒng) API 如下:條件變量API
互斥鎖得不到鎖時(shí),線程會(huì)進(jìn)入休眠,引發(fā)任務(wù)上下文切換,任務(wù)切換涉及一系列耗時(shí)的操作,因此用互斥鎖一旦遇到阻塞切換代價(jià)是十分昂貴的。而自旋鎖阻塞后不會(huì)引發(fā)上下文切換,當(dāng)鎖被其他線程占有時(shí),獲取鎖的線程便會(huì)進(jìn)入自旋,不斷檢測(cè)自旋鎖的狀態(tài),直到得到鎖,所謂的自旋就是循環(huán)等待的意思。自旋鎖在用戶(hù)態(tài)使用的比較少,在內(nèi)核使用的比較多。自旋鎖適用于臨界區(qū)代碼比較短,鎖的持有時(shí)間比較短的場(chǎng)景,否則會(huì)讓其他線程一直等待造成饑餓現(xiàn)象。自旋鎖 API 接口自旋鎖API
可以看到,多線程模型為了保證各個(gè)線程并行工作,需要額外做很多線程間的同步和通知工作,而且線程頻繁的在阻塞和喚醒間切換,我們知道 Linux 下線程是輕量級(jí)線程 LWP ,每次線程切換涉及用戶(hù)態(tài)和內(nèi)核態(tài)的切換,還是很消耗性能的。同樣的場(chǎng)景在協(xié)程模型里是怎么處理的呢?還是用前面的例子,說(shuō)明協(xié)程模型的執(zhí)行流程。
前面講的多線程、多進(jìn)程、協(xié)程都還只是軟件層面的提高服務(wù)處理能力。真正硬核的是從硬件層面提高處理能力,增加 CPU 物理核心數(shù)目,當(dāng)然硬件都是有成本的,所以只有軟件層面已經(jīng)充分榨干性能才會(huì)考慮增加硬件。不過(guò),老板有錢(qián)買(mǎi)最好最貴的服務(wù)器另說(shuō),這是人民幣玩家和窮逼玩家的區(qū)別了,軟件工程師留下了貧困的淚水。
增加機(jī)器核心數(shù)
CPU領(lǐng)域有一條摩爾定律:大概 18 個(gè)月會(huì)將芯片的性能提高一倍?,F(xiàn)在這個(gè)定律變的越來(lái)越難以突破,CPU 晶體管密度工作頻率很難再提高,轉(zhuǎn)而通過(guò)增加 CPU 核心數(shù)目的方式提高處理器性能。CPU?|圖片來(lái)源:www.hippopx.com License CC0目前商用服務(wù)器架構(gòu)基本都是多核處理器,多核的處理器能夠真正做到程序并行運(yùn)行,處理效率大幅度提升,那該如何查看 CPU 核心數(shù)目呢?對(duì)于 Windows 操作系統(tǒng),打開(kāi)任務(wù)管理器,通過(guò)界面的「內(nèi)核」和「邏輯處理器」能看到。windows 查看核心
查看 cpu 核心數(shù)
對(duì)于 Linux 操作系統(tǒng),通過(guò)下面 2 種方式查看 CPU 核心相關(guān)信息。
1. 通過(guò)cpuinfo文件查看
使用cat /proc/cpuinfo查看 cpu 核心信息,如下兩個(gè)信息:
processor,指明第幾個(gè)cpu處理器
cpu cores,指明每個(gè)處理器的核心數(shù)
cpuinfo 輸出示例:cpuinfo
2. 通過(guò)編程接口查看
除了上面以文件的形式查看 cpu 核心信息之外,系統(tǒng)還提供了編程接口可以查詢(xún),系統(tǒng) API 如下。查看核數(shù)API
CPU親和性
CPU 親和性是綁定某一進(jìn)程或線程到特定的 CPU 或 CPU 集合,從而使得該進(jìn)程或線程只能被調(diào)度運(yùn)行在綁定的 CPU或 CPU 集合上。
為什么要設(shè)置 CPU 親和性綁定 CPU 呢?理論上進(jìn)程上一次運(yùn)行后的上下文信息會(huì)保留在 CPU 的緩存中,如果下一次仍然將該進(jìn)程調(diào)度到同一個(gè) CPU 上,就能避免緩存未命中對(duì) CPU 處理性能的影響,從而使得進(jìn)程的運(yùn)行更加高效。
假如某些進(jìn)程或線程是 CPU 密集型的,不希望被頻繁調(diào)度,又或者你有其他特殊需求,不希望進(jìn)程或線程被調(diào)度在不同 CPU 之間頻繁切換,則可以將該進(jìn)程或線程綁定到特定的 CPU 上 ,可以在特定場(chǎng)景下優(yōu)化程序性能。
綁定進(jìn)程
在多進(jìn)程模型中,綁定進(jìn)程到特定的核心,下面是綁定進(jìn)程的系統(tǒng) API
本文從程序任務(wù)類(lèi)型出發(fā),區(qū)分任務(wù)為 CPU 密集型和 IO 密集型兩大類(lèi)。接著分別說(shuō)明提高基于這兩類(lèi)任務(wù)的服務(wù)性能方法,分為軟件層面的方法和硬件層面的方法。其中軟件層面主要講述利用多進(jìn)程、多線程以及協(xié)程模型,當(dāng)然現(xiàn)有的技術(shù)還有 IO 多路復(fù)用、異步 IO 、池化技術(shù)等方案,講到多線程和多進(jìn)程,順勢(shì)說(shuō)明了進(jìn)程間通信和線程間同步互斥技術(shù)。第二部分,講解了從硬件層面提高服務(wù)性能:提高機(jī)器核心數(shù),并教你如何查看 CPU 核心數(shù)的方法。最后,還可以通過(guò)軟硬結(jié)合的方式,把硬件核心綁定到指定進(jìn)程或者線程執(zhí)行,最大程度的利用 CPU 性能。希望通過(guò)本文的學(xué)習(xí),讀者對(duì)高性能服務(wù)模型有個(gè)初步的了解,并能對(duì)服務(wù)優(yōu)化的方法和利弊舉例一二,就是本文的價(jià)值所在。