0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線(xiàn)課程
  • 觀(guān)看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

如何理解高性能服務(wù)器的高性能、高并發(fā)?

GPU視覺(jué)識(shí)別 ? 來(lái)源:GPU視覺(jué)識(shí)別 ? 作者:GPU視覺(jué)識(shí)別 ? 2023-01-13 09:45 ? 次閱讀

線(xiàn)程 | 同步 | 異步 | 異構(gòu)

協(xié)程 | 進(jìn)程 | 同構(gòu) | 線(xiàn)程池

當(dāng)前,隨著“東數(shù)西算”政策的落地,算力時(shí)代正在全面開(kāi)啟。 隨著機(jī)器學(xué)習(xí)、深度學(xué)習(xí)的快速發(fā)展,人們對(duì)高性能服務(wù)器這一概念不再陌生。 伴隨著數(shù)據(jù)分析、數(shù)據(jù)挖掘數(shù)目的不斷增大,傳統(tǒng)的風(fēng)冷散熱方式已經(jīng)不足以滿(mǎn)足散熱需要,這就需要新興的液冷散熱技術(shù)以此滿(mǎn)足節(jié)能減排、靜音高效的需求。

作為國(guó)內(nèi)品牌服務(wù)器廠(chǎng)商,藍(lán)海大腦液冷GPU服務(wù)器擁有大規(guī)模并行處理能力和無(wú)與倫比的靈活性。 它主要用于為計(jì)算密集型應(yīng)用程序提供足夠的處理能力。 GPU的優(yōu)勢(shì)在于可以由CPU運(yùn)行應(yīng)用程序代碼,同時(shí)圖形處理單元(GPU)可以處理大規(guī)模并行架構(gòu)的計(jì)算密集型任務(wù)。 GPU服務(wù)器是遙感測(cè)繪、醫(yī)藥研發(fā)、生命科學(xué)和高性能計(jì)算的理想選擇。

本文將為大家全面介紹高性能GPU服務(wù)器所涉及技術(shù)以及如何搭建。

線(xiàn)程與線(xiàn)程池

下面將從CPU開(kāi)始路來(lái)到常用的線(xiàn)程池,從底層到上層、從硬件到軟件。

一、CPU

對(duì)此大家可能會(huì)有疑問(wèn),講多線(xiàn)程為什么要從CPU開(kāi)始? 實(shí)際上CPU并沒(méi)有線(xiàn)程、進(jìn)程之類(lèi)的概念。 CPU所作的就是從內(nèi)存中取出指令——執(zhí)行指令,然后回到1。

poYBAGPAt4GAUDnrAACRrBWX_Co680.png

1、CPU從哪里取出指令

就是我們熟知的程序計(jì)數(shù)器,在這里大家不要把寄存器想的太神秘,可以簡(jiǎn)單的將寄存器理解為內(nèi)存,只不過(guò)存取速度更快而已。

2、PC寄存器中存放的是什么?

指令(CPU將要執(zhí)行的下一條指令)在內(nèi)存中的地址

poYBAGPAt4GAR3tiAABLKqJvkiw928.png

3、誰(shuí)來(lái)改變PC寄存器中的指令地址?

由于大部分情況下CPU都是一條接一條順序執(zhí)行,所以

之前PC寄存器中的地址默認(rèn)是自動(dòng)加1。 但

當(dāng)遇到if、else時(shí),這種順序執(zhí)行就被打破了,為了正確的跳轉(zhuǎn)到需要執(zhí)行的指令,CPU在執(zhí)行這類(lèi)指令時(shí)會(huì)根據(jù)計(jì)算結(jié)果來(lái)動(dòng)態(tài)改變PC寄存器中的值。

4、PC中的初始值是怎么被設(shè)置的?

CPU執(zhí)行的指令來(lái)自?xún)?nèi)存,內(nèi)存中的指令來(lái)自于磁盤(pán)中保存的可執(zhí)行程序加載,磁盤(pán)中可執(zhí)行程序是由編譯器生成的,編譯器從定義的函數(shù)生成的機(jī)器指令。

pYYBAGPAt4KANadxAABj2ZjEv_k505.png

二、從CPU到操作系統(tǒng)

從上面我們明白了CPU的工作原理,如果想讓CPU執(zhí)行某個(gè)函數(shù),只需把函數(shù)對(duì)應(yīng)的第一條機(jī)器執(zhí)行裝入PC寄存器就可以了,這樣即使沒(méi)有操作系統(tǒng)也可以讓CPU執(zhí)行程序,雖然可行但這是一個(gè)非常繁瑣的過(guò)程(1、在內(nèi)存中找到一塊大小合適的區(qū)域裝入程序;2、找到函數(shù)入口,設(shè)置好PC寄存器讓CPU開(kāi)始執(zhí)行程序)。

機(jī)器指令由于需加載到內(nèi)存中執(zhí)行所以需要記錄下內(nèi)存的起始地址和長(zhǎng)度;同時(shí)要找到函數(shù)的入口地址并寫(xiě)到PC寄存器中。

數(shù)據(jù)結(jié)構(gòu)大致如下:

三、從單核到多核,如何充分利用多核

如果一個(gè)程序需要充分利用多核就會(huì)遇到以下問(wèn)題:

1、進(jìn)程是需要占用內(nèi)存空間的(從上一節(jié)到這一節(jié)),如果多個(gè)進(jìn)程基于同一個(gè)可執(zhí)行程序,那么這些進(jìn)程其內(nèi)存區(qū)域中的內(nèi)容幾乎完全相同,顯然會(huì)造成內(nèi)存浪費(fèi);

2、當(dāng)計(jì)算機(jī)處理的任務(wù)比較復(fù)雜時(shí)就會(huì)涉及到進(jìn)程間通信,但是由于各個(gè)進(jìn)程處于不同的內(nèi)存地址空間,而進(jìn)程間通信需要借助操作系統(tǒng),在增大編程難度的同時(shí)也增加了系統(tǒng)開(kāi)銷(xiāo)。

四、從進(jìn)程到線(xiàn)程

進(jìn)程到線(xiàn)程即內(nèi)存中的一段區(qū)域,該區(qū)域保存了CPU執(zhí)行的機(jī)器指令以及函數(shù)運(yùn)行時(shí)的堆棧信息。要想讓進(jìn)程運(yùn)行,就把main函數(shù)的第一條機(jī)器指令地址寫(xiě)入PC寄存器。

poYBAGPAt4KANmvDAAApaOgbIHI774.png

進(jìn)程的缺點(diǎn)在于只有一個(gè)入口函數(shù)(main函數(shù)),進(jìn)程中的機(jī)器指令只能被一個(gè)CPU執(zhí)行,那么有沒(méi)有辦法讓多個(gè)CPU來(lái)執(zhí)行同一個(gè)進(jìn)程中的機(jī)器指令呢?可以將main函數(shù)的第一條指令地址寫(xiě)入PC寄存器。

main函數(shù)和

其它函數(shù)沒(méi)什么區(qū)別,其特殊之處無(wú)非在于是CPU執(zhí)行的第一個(gè)函數(shù)。

當(dāng)把PC寄存器指向非main函數(shù)時(shí),線(xiàn)程就誕生了。

pYYBAGPAt4KAXyY2AAA9ftxZG-c511.png

至此一個(gè)進(jìn)程內(nèi)可以有多個(gè)入口函數(shù),也就是說(shuō)屬于同一個(gè)進(jìn)程中的機(jī)器指令可以被多個(gè)CPU同時(shí)執(zhí)行。

poYBAGPAt4KANPekAAAmW335fTU841.png

多個(gè)CPU可以在同一個(gè)屋檐下(進(jìn)程占用的內(nèi)存區(qū)域)同時(shí)執(zhí)行屬于該進(jìn)程的多個(gè)入口函數(shù)。操作系統(tǒng)為每個(gè)進(jìn)程維護(hù)一堆信息,用來(lái)記錄進(jìn)程所處的內(nèi)存空間等,這堆信息記為數(shù)據(jù)集A。同樣的,操作系統(tǒng)也為線(xiàn)程維護(hù)一堆信息,用來(lái)記錄線(xiàn)程的入口函數(shù)或者棧信息等,這堆數(shù)據(jù)記為數(shù)據(jù)集B。

顯然數(shù)據(jù)集B要比數(shù)據(jù)A的量要少,由于線(xiàn)程是運(yùn)行在所處進(jìn)程的地址空間在程序啟動(dòng)時(shí)已經(jīng)創(chuàng)建完畢,同時(shí)線(xiàn)程是程序在運(yùn)行期間創(chuàng)建的(進(jìn)程啟動(dòng)后),所以當(dāng)線(xiàn)程開(kāi)始運(yùn)行的時(shí)候這塊地址空間就已經(jīng)存在了,線(xiàn)程可以直接使用。

值得一提的是,有了線(xiàn)程這個(gè)概念后,只需要進(jìn)程開(kāi)啟后創(chuàng)建多個(gè)線(xiàn)程就可以讓所有CPU都忙起來(lái),這就是所謂高性能、高并發(fā)的根本所在。

poYBAGPAt4KANAfpAAA-QZvcbUs016.png

另外值得注意的一點(diǎn)是:由于各個(gè)線(xiàn)程共享進(jìn)程的內(nèi)存地址空間,所以線(xiàn)程之間的通信無(wú)需借助操作系統(tǒng),這給工作人員帶來(lái)了便利同時(shí)也有不足之處。多線(xiàn)程遇到的多數(shù)問(wèn)題都出自于線(xiàn)程間通信太方便以至于非常容易出錯(cuò)。出錯(cuò)的根源在于CPU執(zhí)行指令時(shí)沒(méi)有線(xiàn)程的概念,多線(xiàn)程編程面臨的互斥與同步問(wèn)題需要解決。

最后需要注意的是:雖然前面關(guān)于線(xiàn)程講解使用的圖中用了多個(gè)CPU,但并不一定要有多核才能使用多線(xiàn)程,在單核的情況下一樣可以創(chuàng)建出多個(gè)線(xiàn)程,主要是由于線(xiàn)程是操作系統(tǒng)層面的實(shí)現(xiàn),和有多少個(gè)核心是沒(méi)有關(guān)系的,CPU在執(zhí)行機(jī)器指令時(shí)也意識(shí)不到執(zhí)行的機(jī)器指令屬于哪個(gè)線(xiàn)程。即使在只有一個(gè)CPU的情況下,操作系統(tǒng)也可以通過(guò)線(xiàn)程調(diào)度讓各個(gè)線(xiàn)程“同時(shí)”向前推進(jìn),即將CPU的時(shí)間片在各個(gè)線(xiàn)程之間來(lái)回分配,這樣多個(gè)線(xiàn)程看起來(lái)就是“同時(shí)”運(yùn)行了,但實(shí)際上任意時(shí)刻還是只有一個(gè)線(xiàn)程在運(yùn)行。

五、線(xiàn)程與內(nèi)存

前面介紹了線(xiàn)程和CPU的關(guān)系,也就是把CPU的PC寄存器指向線(xiàn)程的入口函數(shù),這樣線(xiàn)程就可以運(yùn)行起來(lái)了。

無(wú)論使用任何編程語(yǔ)言,創(chuàng)建一個(gè)線(xiàn)程大體相同:

函數(shù)在被執(zhí)行的時(shí)產(chǎn)生的數(shù)據(jù)包括:函數(shù)參數(shù)、局部變量、返回地址等信息。這些信息保存在棧中,線(xiàn)程這個(gè)概念還沒(méi)有出現(xiàn)時(shí)進(jìn)程中只有一個(gè)執(zhí)行流,因此只有一個(gè)棧,這個(gè)棧的棧底就是進(jìn)程的入口函數(shù),也就是main函數(shù)。

假設(shè)main函數(shù)調(diào)用了funA,funcA又調(diào)用了funcB,如圖所示:

poYBAGPAt4KAce0OAAAzN8fnnuI762.png

有了線(xiàn)程以后一個(gè)進(jìn)程中就存在多個(gè)執(zhí)行入口,即同時(shí)存在多個(gè)執(zhí)行流,只有一個(gè)執(zhí)行流的進(jìn)程需要一個(gè)棧來(lái)保存運(yùn)行時(shí)信息,顯然有多個(gè)執(zhí)行流時(shí)就需要有多個(gè)棧來(lái)保存各個(gè)執(zhí)行流的信息,也就是說(shuō)操作系統(tǒng)要為每個(gè)線(xiàn)程在進(jìn)程的地址空間中分配一個(gè)棧,即每個(gè)線(xiàn)程都有獨(dú)屬于自己的棧,能意識(shí)到這一點(diǎn)是極其關(guān)鍵的。同時(shí)創(chuàng)建線(xiàn)程是要消耗進(jìn)程內(nèi)存空間的。

六、線(xiàn)程的使用

從生命周期的角度講,線(xiàn)程要處理的任務(wù)有兩類(lèi):長(zhǎng)任務(wù)和短任務(wù)。

1、長(zhǎng)任務(wù)(long-lived tasks)

顧名思義,就是任務(wù)存活的時(shí)間長(zhǎng)。以常用的word為例,在word中編輯的文字需要保存在磁盤(pán)上,往磁盤(pán)上寫(xiě)數(shù)據(jù)就是一個(gè)任務(wù),這時(shí)一個(gè)比較好的方法就是專(zhuān)門(mén)創(chuàng)建一個(gè)寫(xiě)磁盤(pán)的線(xiàn)程,該線(xiàn)程的生命周期和word進(jìn)程是一樣的,只要打開(kāi)word就要?jiǎng)?chuàng)建出該線(xiàn)程,當(dāng)用戶(hù)關(guān)閉word時(shí)該線(xiàn)程才會(huì)被銷(xiāo)毀,這就是長(zhǎng)任務(wù)。長(zhǎng)任務(wù)非常適合創(chuàng)建專(zhuān)用的線(xiàn)程來(lái)處理某些特定任務(wù)。

2、短任務(wù)(short-lived tasks)

即任務(wù)的處理時(shí)間短,如一次網(wǎng)絡(luò)請(qǐng)求、一次數(shù)據(jù)庫(kù)查詢(xún)等。這種任務(wù)可以在短時(shí)間內(nèi)快速處理完成。因此短任務(wù)多見(jiàn)于各種Server,像web server、database server、file server、mail server等。該場(chǎng)景有任務(wù)處理所需時(shí)間短和任務(wù)數(shù)量巨大的兩個(gè)特點(diǎn)。

poYBAGPAt4KAIuMnAABmYd0A0kk263.png

這種工作方法可對(duì)長(zhǎng)任務(wù)來(lái)說(shuō)很好,但是對(duì)于大量的短任務(wù)來(lái)說(shuō)雖然實(shí)現(xiàn)簡(jiǎn)單但卻有其缺點(diǎn):

1)線(xiàn)程是操作系統(tǒng)中的概念,因此創(chuàng)建線(xiàn)程需要借助操作系統(tǒng)來(lái)完成,操作系統(tǒng)創(chuàng)建和銷(xiāo)毀線(xiàn)程是需要消耗時(shí)間的;

2)每個(gè)線(xiàn)程需要有自己獨(dú)立的棧,因此當(dāng)創(chuàng)建大量線(xiàn)程時(shí)會(huì)消耗過(guò)多的內(nèi)存等系統(tǒng)資源。

這就好比一個(gè)工廠(chǎng)老板手里有很多訂單,每來(lái)一批訂單就要招一批工人,生產(chǎn)的產(chǎn)品非常簡(jiǎn)單,工人們很快就能處理完,處理完這批訂單后就把這些工人辭掉,當(dāng)有新的訂單時(shí)再招一遍工人,干活兒5分鐘招人10小時(shí),如果你不是勵(lì)志要讓企業(yè)倒閉的話(huà)大概是不會(huì)這么做到的。因此一個(gè)更好的策略就是招一批人后就地養(yǎng)著,有訂單時(shí)處理訂單,沒(méi)有訂單時(shí)大家可以待著。

這就是線(xiàn)程池的由來(lái)。

七、從多線(xiàn)程到線(xiàn)程池

線(xiàn)程池的無(wú)非就是創(chuàng)建一批線(xiàn)程之后就不再釋放,有任務(wù)就提交給線(xiàn)程處理,因此無(wú)需頻繁的創(chuàng)建、銷(xiāo)毀線(xiàn)程,同時(shí)由于線(xiàn)程池中的線(xiàn)程個(gè)數(shù)通常是固定的,也不會(huì)消耗過(guò)多的內(nèi)存。

八、線(xiàn)程池是如何工作的?

一般來(lái)說(shuō)提交給線(xiàn)程池的任務(wù)包含需要被處理的數(shù)據(jù)和處理數(shù)據(jù)的函數(shù)兩部分。

偽碼描述一下:

線(xiàn)程池中的線(xiàn)程會(huì)阻塞在隊(duì)列上,當(dāng)工作人員向隊(duì)列中寫(xiě)入數(shù)據(jù)后,線(xiàn)程池中的某個(gè)線(xiàn)程會(huì)被喚醒,該線(xiàn)程從隊(duì)列中取出上述結(jié)構(gòu)體(或者對(duì)象),以結(jié)構(gòu)體(或者對(duì)象)中的數(shù)據(jù)為參數(shù)并調(diào)用處理函數(shù)。

偽碼如下:

八、線(xiàn)程池中線(xiàn)程的數(shù)量

眾所周知線(xiàn)程池的線(xiàn)程過(guò)少就不能充分利用CPU,線(xiàn)程創(chuàng)建的過(guò)多反而會(huì)造成系統(tǒng)性能下降,內(nèi)存占用過(guò)多,線(xiàn)程切換造成的消耗等等。因此線(xiàn)程的數(shù)量既不能太多也不能太少,到底該是多少呢?

從處理任務(wù)所需要的資源角度看有CPU密集型和I/O密集型兩種類(lèi)型。

1、CPU密集型

所謂CPU密集型是指說(shuō)理任務(wù)不需要依賴(lài)外部I/O,比如科學(xué)計(jì)算、矩陣運(yùn)算等。在這種情況下只要線(xiàn)程的數(shù)量和核數(shù)基本相同就可以充分利用CPU資源。

poYBAGPAt4KABNESAAAgn6krlkg126.png

2、I/O密集型

這一類(lèi)任務(wù)可能計(jì)算部分所占用時(shí)間不多,大部分時(shí)間都用在磁盤(pán)I/O、網(wǎng)絡(luò)I/O等方面。

pYYBAGPAt4GAR8rnAAAg52fICik201.png

工作人員需要利用性能測(cè)試工具評(píng)估出用在I/O等待上的時(shí)間,這里記為WT(wait time),以及CPU計(jì)算所需要的時(shí)間,這里記為CT(computing time),那么對(duì)于一個(gè)N核的系統(tǒng),合適的線(xiàn)程數(shù)大概是 N * (1 + WT/CT) ,假設(shè)I/O等待時(shí)間和計(jì)算時(shí)間相同,那么大概需要2N個(gè)線(xiàn)程才能充分利用CPU資源,注意這只是一個(gè)理論值,具體設(shè)置多少需要根據(jù)真實(shí)的業(yè)務(wù)場(chǎng)景進(jìn)行測(cè)試。

當(dāng)然充分利用CPU不是唯一需要考慮的點(diǎn),隨著線(xiàn)程數(shù)量的增多,內(nèi)存占用、系統(tǒng)調(diào)度、打開(kāi)的文件數(shù)量、打開(kāi)的socker數(shù)量以及打開(kāi)的數(shù)據(jù)庫(kù)鏈接等等是都需要考慮的。所以沒(méi)有萬(wàn)能公式,要具體情況具體分析。

九、使用線(xiàn)程前需要考慮的因素

1、充分理解任務(wù)是長(zhǎng)任務(wù)還是短任務(wù)、是CPU密集型還是I/O密集型,如果兩種都有,那么一種可能更好的辦法是把這兩類(lèi)任務(wù)放到不同的線(xiàn)程池。

2、如果線(xiàn)程池中的任務(wù)有I/O操作,那么務(wù)必對(duì)此任務(wù)設(shè)置超時(shí),否則處理該任務(wù)的線(xiàn)程可能會(huì)一直阻塞下去;

4、線(xiàn)程池中的任務(wù)不要同步等待其它任務(wù)的結(jié)果。

I/O與零拷貝技術(shù)

一、什么是I/O?

I/O就是簡(jiǎn)單的數(shù)據(jù)Copy,如果數(shù)據(jù)從外部設(shè)備copy到內(nèi)存中就是Input。如果數(shù)據(jù)是內(nèi)存copy到外部設(shè)備則是Output。內(nèi)存與外部設(shè)備之間不嫌麻煩的來(lái)回copy數(shù)據(jù)就是Input and Output,簡(jiǎn)稱(chēng)I/O(Input/Output)。

poYBAGPAt4KALO_WAACfWnaawiY849.jpg

二、I/O與CPU

簡(jiǎn)單來(lái)說(shuō):CPU執(zhí)行機(jī)器指令的速度是納秒級(jí)別的,而通常的I/O比如磁盤(pán)操作,一次磁盤(pán)seek大概在毫秒級(jí)別,因此如果我們把CPU的速度比作戰(zhàn)斗機(jī)的話(huà),那么I/O操作的速度就是肯德雞。

也就是說(shuō)當(dāng)程序跑起來(lái)時(shí)(CPU執(zhí)行機(jī)器指令),其速度是要遠(yuǎn)遠(yuǎn)快于I/O速度。那么接下來(lái)的問(wèn)題就是二者速度相差這么大,該如何設(shè)計(jì)、更加合理的高效利用系統(tǒng)資源呢?

既然有速度差異,進(jìn)程在執(zhí)行完I/O操作前不能繼續(xù)向前推進(jìn),那就只有等待(wait)。

三、執(zhí)行I/O時(shí)底層都發(fā)生了什么

在支持線(xiàn)程的操作系統(tǒng)中,實(shí)際上被調(diào)度的是線(xiàn)程而不是進(jìn)程,為了更加清晰的理解I/O過(guò)程,暫時(shí)假設(shè)操作系統(tǒng)只有進(jìn)程這樣的概念,先不去考慮線(xiàn)程。

如下圖所示,現(xiàn)在內(nèi)存中有兩個(gè)進(jìn)程,進(jìn)程A和進(jìn)程B,當(dāng)前進(jìn)程A正在運(yùn)行。如下圖所示:

pYYBAGPAt4KAYt25AABEuvp-fNA932.png

進(jìn)程A中有一段讀取文件的代碼,不管在什么語(yǔ)言中通常定義一個(gè)用來(lái)裝數(shù)據(jù)的buff,然后調(diào)用read之類(lèi)的函數(shù)。

注意:與CPU執(zhí)行指令的速度相比,I/O操作操作是非常慢的,因此操作系統(tǒng)是不可能把寶貴的CPU計(jì)算資源浪費(fèi)在無(wú)謂的等待上的。由于外部設(shè)備執(zhí)行I/O操作是相當(dāng)慢的,所以在I/O操作完成之前進(jìn)程是無(wú)法繼續(xù)向前推進(jìn)的,這就是所謂的阻塞,即block。

只需記錄下當(dāng)前進(jìn)程的運(yùn)行狀態(tài)并把CPU的PC寄存器指向其它進(jìn)程的指令就

操作系統(tǒng)檢測(cè)到進(jìn)程向I/O設(shè)備發(fā)起請(qǐng)求后就暫停進(jìn)程的運(yùn)行

。進(jìn)程有暫停就會(huì)有繼續(xù)執(zhí)行,因此操作系統(tǒng)必須保存被暫停的進(jìn)程以備后續(xù)繼續(xù)執(zhí)行,顯然我們可以用隊(duì)列來(lái)保存被暫停執(zhí)行的進(jìn)程。

poYBAGPAt4KAIICBAABb1qWMfiM798.png

如上圖所示,操作系統(tǒng)已經(jīng)向磁盤(pán)發(fā)送I/O請(qǐng)求,因此磁盤(pán)driver開(kāi)始將磁盤(pán)中的數(shù)據(jù)copy到進(jìn)程A的buff中。雖然這時(shí)進(jìn)程A已經(jīng)被暫停執(zhí)行了,但這并不妨礙磁盤(pán)向內(nèi)存中copy數(shù)據(jù)。過(guò)程如下圖所示:

poYBAGPAt4KAVHH6AABf6xWYqj8841.png

操作系統(tǒng)中除了有阻塞隊(duì)列之外也有就緒隊(duì)列,所謂就緒隊(duì)列是指隊(duì)列里的進(jìn)程準(zhǔn)備就緒可以被CPU執(zhí)行了。在即使只有1個(gè)核的機(jī)器上也可以創(chuàng)建出成千上萬(wàn)個(gè)進(jìn)程,CPU不可能同時(shí)執(zhí)行這么多的進(jìn)程,因此必然存在這樣的進(jìn)程,即使其一切準(zhǔn)備就緒也不能被分配到計(jì)算資源,這樣的進(jìn)程就被放到了就緒隊(duì)列。

pYYBAGPAt4KAPJ92AABy_O2xHNc532.png

由于就緒隊(duì)列中還有嗷嗷待哺的進(jìn)程B,所以當(dāng)進(jìn)程A被暫停執(zhí)行后CPU是不可以閑下來(lái)的。這時(shí)操作系統(tǒng)開(kāi)始在就緒隊(duì)列中找下一個(gè)可以執(zhí)行的進(jìn)程,也就是這里的進(jìn)程B。此時(shí)操作系統(tǒng)將進(jìn)程B從就緒隊(duì)列中取出,找出進(jìn)程B被暫停時(shí)執(zhí)行到的機(jī)器指令的位置,然后將CPU的PC寄存器指向該位置,這樣進(jìn)程B就開(kāi)始運(yùn)行啦。

pYYBAGPAt4KAYiFhAABuXjxJy8I019.png

如上圖所示,進(jìn)程B在被CPU執(zhí)行,磁盤(pán)在向進(jìn)程A的內(nèi)存空間中copy數(shù)據(jù),數(shù)據(jù)copy和指令執(zhí)行在同時(shí)進(jìn)行,在操作系統(tǒng)的調(diào)度下,CPU、磁盤(pán)都得到了充分的利用。此后磁盤(pán)將全部數(shù)據(jù)都copy到了進(jìn)程A的內(nèi)存中,操作系統(tǒng)接收到磁盤(pán)中斷后發(fā)現(xiàn)數(shù)據(jù)copy完畢,進(jìn)程A重新獲得繼續(xù)運(yùn)行的資格,操作系統(tǒng)把進(jìn)程A從阻塞隊(duì)列放到了就緒隊(duì)列當(dāng)中。

pYYBAGPAt4KAAO1iAABmWzVw4No756.png

此后進(jìn)程B繼續(xù)執(zhí)行,進(jìn)程A繼續(xù)等待,進(jìn)程B執(zhí)行了一會(huì)兒后操作系統(tǒng)認(rèn)為進(jìn)程B執(zhí)行的時(shí)間夠長(zhǎng)了,因此把進(jìn)程B放到就緒隊(duì)列,把進(jìn)程A取出并繼續(xù)執(zhí)行。操作系統(tǒng)把進(jìn)程B放到的是就緒隊(duì)列,因此進(jìn)程B被暫停運(yùn)行僅僅是因?yàn)闀r(shí)間片到了而不是因?yàn)榘l(fā)起I/O請(qǐng)求被阻塞。

pYYBAGPAt4KAQ_sTAABphwHVJHM245.png

四、零拷貝(Zero-copy)

值得注意的一點(diǎn)是:上面的講解中直接把磁盤(pán)數(shù)據(jù)copy到了進(jìn)程空間中,但實(shí)際上一般情況下I/O數(shù)據(jù)是要首先copy到操作系統(tǒng)內(nèi)部,然后操作系統(tǒng)再copy到進(jìn)程空間中。性能要求很高的場(chǎng)景其實(shí)也是可以繞過(guò)操作系統(tǒng)直接進(jìn)行數(shù)據(jù)copy,這種繞過(guò)操作系統(tǒng)直接進(jìn)行數(shù)據(jù)copy的技術(shù)被稱(chēng)為零拷貝(Zero-copy)。

I/O多路復(fù)用

本文我們?cè)敿?xì)講解什么是I/O多路復(fù)用以及使用方法,這其中以epoll為代表的I/O多路復(fù)用(基于事件驅(qū)動(dòng))技術(shù)使用非常廣泛,實(shí)際上你會(huì)發(fā)現(xiàn)但凡涉及到高并發(fā)、高性能的場(chǎng)景基本上都能見(jiàn)到事件驅(qū)動(dòng)的編程方法。

一、什么是文件?

Linux世界中文件是一個(gè)很簡(jiǎn)單的概念,只需要將其理解為一個(gè)N byte的序列就可以了:

b1, b2, b3, b4, ....... bN

實(shí)際上所有的I/O設(shè)備都被抽象了,一切皆文件(Everything is File),磁盤(pán)、網(wǎng)絡(luò)數(shù)據(jù)、終端,甚至進(jìn)程間通信工具管道pipe等都被當(dāng)做文件對(duì)待。

pYYBAGPAt4GANIgNAABtxe1O5TM682.png

常用的I/O操作接口一般有以下幾類(lèi):

1、打開(kāi)文件,open;

2、改變讀寫(xiě)位置,seek;

3、文件讀寫(xiě),read、write;

4、關(guān)閉文件,close。

二、什么是文件描述符?

在上文中我們講到:要想進(jìn)行I/O讀操作,像磁盤(pán)數(shù)據(jù),需要指定一個(gè)buff用來(lái)裝入數(shù)據(jù)。在Linux世界要想使用文件,需要借助一個(gè)號(hào)碼,根據(jù)“弄不懂原則”,這個(gè)號(hào)碼就被稱(chēng)為了文件描述符(file descriptors),在Linux世界中鼎鼎大名,其道理和上面那個(gè)排隊(duì)號(hào)碼一樣。文件描述僅僅就是一個(gè)數(shù)字而已,但是通過(guò)這個(gè)數(shù)字我們可以操作一個(gè)打開(kāi)的文件。

poYBAGPAt4KACJ46AABeOs3qbNo223.png

有了文件描述符,進(jìn)程可以對(duì)文件一無(wú)所知,比如文件在磁盤(pán)的什么位置、加載到內(nèi)存中又是怎樣管理的等等,這些信息統(tǒng)統(tǒng)交由操作系統(tǒng)打理,進(jìn)程無(wú)需關(guān)心,操作系統(tǒng)只需要給進(jìn)程一個(gè)文件描述符就足夠了。

三、文件描述符太多了怎么辦?

從上文中我們知道,所有I/O操作都可以通過(guò)文件樣的概念來(lái)進(jìn)行,這當(dāng)然包括網(wǎng)絡(luò)通信。

如果你有一個(gè)IM服務(wù)器,當(dāng)三次握手建議長(zhǎng)連接成功以后,我們會(huì)調(diào)用accept來(lái)獲取一個(gè)鏈接,調(diào)用該函數(shù)我們同樣會(huì)得到一個(gè)文件描述符,通過(guò)這個(gè)文件描述符就可以處理客戶(hù)端發(fā)送的聊天消息并且把消息轉(zhuǎn)發(fā)給接收者。

也就是說(shuō),通過(guò)這個(gè)描述符就可以和客戶(hù)端進(jìn)行通信了:

// 通過(guò)accept獲取客戶(hù)端的文件描述符
int conn_fd = accept(...);

Server端的處理邏輯通常是接收客戶(hù)端消息數(shù)據(jù),然后執(zhí)行轉(zhuǎn)發(fā)(給接收者)邏輯:

if(read(conn_fd, msg_buff) > 0) {
do_transfer(msg_buff);
}

既然主題是高并發(fā),那么Server端就不可能只和一個(gè)客戶(hù)端通信,而是可能會(huì)同時(shí)和成千上萬(wàn)個(gè)客戶(hù)端進(jìn)行通信。這時(shí)需要處理不再是一個(gè)描述符這么簡(jiǎn)單,而是有可能要處理成千上萬(wàn)個(gè)描述符。為了不讓問(wèn)題一上來(lái)就過(guò)于復(fù)雜先簡(jiǎn)單化,假設(shè)只同時(shí)處理兩個(gè)客戶(hù)端的請(qǐng)求。

有的同學(xué)可能會(huì)說(shuō),這還不簡(jiǎn)單,這樣寫(xiě)不就行了:

if(read(socket_fd1, buff) > 0) { // 處理第一個(gè)
do_transfer();
}
if(read(socket_fd2, buff) > 0) { // 處理第二個(gè)
do_transfer();

如果此時(shí)沒(méi)有數(shù)據(jù)可讀那么進(jìn)程會(huì)被阻塞而暫停運(yùn)行。這時(shí)我們就無(wú)法處理第二個(gè)請(qǐng)求了,即使第二個(gè)請(qǐng)求的數(shù)據(jù)已經(jīng)就位,這也就意味著處理某一個(gè)客戶(hù)端時(shí)由于進(jìn)程被阻塞導(dǎo)致剩下的所有其它客戶(hù)端必須等待,在同時(shí)處理幾萬(wàn)客戶(hù)端的server上。這顯然是不能容忍的。

聰明的你一定會(huì)想到使用多線(xiàn)程:為每個(gè)客戶(hù)端請(qǐng)求開(kāi)啟一個(gè)線(xiàn)程,這樣一個(gè)客戶(hù)端被阻塞就不會(huì)影響到處理其它客戶(hù)端的線(xiàn)程了。注意:既然是高并發(fā),那么我們要為成千上萬(wàn)個(gè)請(qǐng)求開(kāi)啟成千上萬(wàn)個(gè)線(xiàn)程嗎,大量創(chuàng)建銷(xiāo)毀線(xiàn)程會(huì)嚴(yán)重影響系統(tǒng)性能。

那么這個(gè)問(wèn)題該怎么解決呢?

這里的關(guān)鍵點(diǎn)在于:我們事先并不知道一個(gè)文件描述對(duì)應(yīng)的I/O設(shè)備是否是可讀的、是否是可寫(xiě)的,在外設(shè)的不可讀或不可寫(xiě)的狀態(tài)下進(jìn)行I/O只會(huì)導(dǎo)致進(jìn)程阻塞被暫停運(yùn)行。

三、I/O多路復(fù)用(I/O multiplexing)

multiplexing一詞多用于通信領(lǐng)域,為了充分利用通信線(xiàn)路,希望在一個(gè)信道中傳輸多路信號(hào),要想在一個(gè)信道中傳輸多路信號(hào)就需要把這多路信號(hào)結(jié)合為一路,將多路信號(hào)組合成一個(gè)信號(hào)的設(shè)備被稱(chēng)為Multiplexer(多路復(fù)用器),顯然接收方接收到這一路組合后的信號(hào)后要恢復(fù)原先的多路信號(hào),這個(gè)設(shè)備被稱(chēng)為Demultiplexer(多路分用器)。

如下圖所示:

pYYBAGPAt4KAbEryAAAoSHU-V9M362.png

所謂I/O多路復(fù)用指的是這樣一個(gè)過(guò)程:

1、拿到一堆文件描述符(不管是網(wǎng)絡(luò)相關(guān)的、還是磁盤(pán)文件相關(guān)等等,任何文件描述符都可以);

2、通過(guò)調(diào)用某個(gè)函數(shù)告訴內(nèi)核:“這個(gè)函數(shù)你先不要返回,你替我監(jiān)視著這些描述符,當(dāng)這堆文件描述符中有可以進(jìn)行I/O讀寫(xiě)操作的時(shí)候你再返回”;

3、當(dāng)調(diào)用的這個(gè)函數(shù)返回后就能知道哪些文件描述符可以進(jìn)行I/O操作了。
**
三、I/O多路復(fù)用三劍客**

由于調(diào)用這些I/O多路復(fù)用函數(shù)時(shí)如果任何一個(gè)需要監(jiān)視的文件描述符都不可讀或者可寫(xiě)那么進(jìn)程會(huì)被阻塞暫停執(zhí)行,直到有文件描述符可讀或者可寫(xiě)才繼續(xù)運(yùn)行。所以L(fǎng)inux上的select、poll、epoll都是阻塞式I/O,也就是同步I/O。

1、select:初出茅廬

在select I/O多路復(fù)用機(jī)制下,需要把想監(jiān)控的文件描述集合通過(guò)函數(shù)參數(shù)的形式告訴select,然后select將這些文件描述符集合拷貝到內(nèi)核中。為了減少這種數(shù)據(jù)拷貝帶來(lái)的性能損耗,Linux內(nèi)核對(duì)集合的大小做了限制,并規(guī)定用戶(hù)監(jiān)控的文件描述集合不能超過(guò)1024個(gè),同時(shí)當(dāng)select返回后,僅僅能知道有些文件描述符可以讀寫(xiě)了。

select的特點(diǎn)

1、能照看的文件描述符數(shù)量有限,不能超過(guò)1024個(gè);

2、用戶(hù)給文件描述符需要拷貝的內(nèi)核中;

3、只能告訴有文件描述符滿(mǎn)足要求但不知道是哪個(gè)。

2、poll:小有所成

poll和select是非常相似,相對(duì)于select的優(yōu)化僅僅在于解決文件描述符不能超過(guò)1024個(gè)的限制,select和poll都會(huì)隨著監(jiān)控的文件描述數(shù)量增加而性能下降,因此不適合高并發(fā)場(chǎng)景。

3、epoll:獨(dú)步天下

在select面臨的三個(gè)問(wèn)題中,文件描述數(shù)量限制已經(jīng)在poll中解決了,剩下的兩個(gè)問(wèn)題呢?

針對(duì)拷貝問(wèn)題

epoll使用的策略是各個(gè)擊破與共享內(nèi)存。文件描述符集合的變化頻率比較低,select和poll頻繁的拷貝整個(gè)集合,epoll通過(guò)引入epoll_ctl很體貼的做到了只操作那些有變化的文件描述符。同時(shí)epoll和內(nèi)核還成為了好朋友,共享了同一塊內(nèi)存,這塊內(nèi)存中保存的就是那些已經(jīng)可讀或者可寫(xiě)的的文件描述符集合,這樣就減少了內(nèi)核和程序的拷貝開(kāi)銷(xiāo)。

針對(duì)需要遍歷文件描述符才能知道哪個(gè)可讀可寫(xiě)的問(wèn)題,epoll使用的策略是在select和poll機(jī)制下:進(jìn)程要親自下場(chǎng)去各個(gè)文件描述符上等待,任何一個(gè)文件描述可讀或者可寫(xiě)就喚醒進(jìn)程,但是進(jìn)程被喚醒后也是一臉懵逼并不知道到底是哪個(gè)文件描述符可讀或可寫(xiě),還要再?gòu)念^到尾檢查一遍。在epoll機(jī)制下進(jìn)程不需要親自下場(chǎng)了,進(jìn)程只要等待在epoll上,epoll代替進(jìn)程去各個(gè)文件描述符上等待,當(dāng)哪個(gè)文件描述符可讀或者可寫(xiě)的時(shí)候就告訴epoll,由epoll記錄。

在epoll這種機(jī)制下,實(shí)際上利用的就是“不要打電話(huà)給我,有需要我會(huì)打給你”這種策略,進(jìn)程不需要一遍一遍麻煩的問(wèn)各個(gè)文件描述符,而是翻身做主人了——“你們這些文件描述符有哪個(gè)可讀或者可寫(xiě)了主動(dòng)報(bào)上來(lái)”。

同步與異步
**
一、同步與異步場(chǎng)景:打電話(huà)與發(fā)郵件**

1、同步

通常打電話(huà)時(shí)都是一個(gè)人在說(shuō)另一個(gè)人聽(tīng),一個(gè)人在說(shuō)的時(shí)候另一個(gè)人等待,等另一個(gè)人說(shuō)完后再接著說(shuō),因此在這個(gè)場(chǎng)景中你可以看到,“依賴(lài)”、“關(guān)聯(lián)”、“等待”這些關(guān)鍵詞出現(xiàn)了,因此打電話(huà)這種溝通方式就是所謂的同步。

pYYBAGPAt4KATn2GAABHxp60zds376.png

2、異步

另一種常用的溝通方式是郵件,因?yàn)闆](méi)有人傻等著你寫(xiě)郵件什么都不做,因此你可以慢慢悠悠的寫(xiě),當(dāng)你在寫(xiě)郵件時(shí)收件人可以去做一些像摸摸魚(yú)啊、上個(gè)廁所、和同時(shí)抱怨一下為什么十一假期不放兩周之類(lèi)有意義的事情。同時(shí)當(dāng)你寫(xiě)完郵件發(fā)出去后也不需要干巴巴的等著對(duì)方回復(fù)什么都不做,你也可以做一些像摸魚(yú)之類(lèi)這樣有意義的事情。

poYBAGPAt4KAXNgbAABbe_LqRH4967.png

在這里,你寫(xiě)郵件別人摸魚(yú),這兩件事又在同時(shí)進(jìn)行,收件人和發(fā)件人都不需要相互等待,發(fā)件人寫(xiě)完郵件的時(shí)候簡(jiǎn)單的點(diǎn)個(gè)發(fā)送就可以了,收件人收到后就可以閱讀啦,收件人和發(fā)件人不需要相互依賴(lài)、不需要相互等待。因此郵件這種溝通方式就是異步的。

二、編程中的同步調(diào)用

一般的函數(shù)調(diào)用都是同步的,就像這樣:

funcA調(diào)用funcB,那么在funcB執(zhí)行完前,funcA中的后續(xù)代碼都不會(huì)被執(zhí)行,也就是說(shuō)funcA必須等待funcB執(zhí)行完成,如下圖所示。

poYBAGPAt4KADAZPAAA18Gc7uis380.png

從上圖中可以看出,在funcB運(yùn)行期間funcA什么都做不了,這就是典型的同步。一般來(lái)說(shuō),像這種同步調(diào)用,funcA和funcB是運(yùn)行在同一個(gè)線(xiàn)程中的,但值得注意的是即使運(yùn)行在兩個(gè)不能線(xiàn)程中的函數(shù)也可以進(jìn)行同步調(diào)用,像我們進(jìn)行IO操作時(shí)實(shí)際上底層是通過(guò)系統(tǒng)調(diào)用的方式向操作系統(tǒng)發(fā)出請(qǐng)求。

pYYBAGPAt4KAWG1wAAA4AORS9xM330.png

如上圖所示,只有當(dāng)read函數(shù)返回后程序才可以被繼續(xù)執(zhí)行。和上面的同步調(diào)用不同的是,函數(shù)和被調(diào)函數(shù)運(yùn)行在不同的線(xiàn)程中。由此我們可以得出結(jié)論,同步調(diào)用和函數(shù)與被調(diào)函數(shù)是否運(yùn)行在同一個(gè)線(xiàn)程是沒(méi)有關(guān)系的。在這里需要再次強(qiáng)調(diào)同步方式下函數(shù)和被調(diào)函數(shù)無(wú)法同時(shí)進(jìn)行。

三、編程中的異步調(diào)用

有同步調(diào)用就有異步調(diào)用。一般來(lái)說(shuō)異步調(diào)用總是和I/O操作等耗時(shí)較高的任務(wù)如影隨形,像磁盤(pán)文件讀寫(xiě)、網(wǎng)絡(luò)數(shù)據(jù)的收發(fā)、數(shù)據(jù)庫(kù)操作等。

在這里以磁盤(pán)文件讀取為例,在read函數(shù)的同步調(diào)用方式下,文件讀取完之前調(diào)用方是無(wú)法繼續(xù)向前推進(jìn)的,但如果read函數(shù)可以異步調(diào)用情況就不一樣了。假如read函數(shù)可以異步調(diào)用的話(huà),即使文件還沒(méi)有讀取完成,read函數(shù)也可以立即返回。

pYYBAGPAt4KAPYFVAAA2sskmMYs650.png

如上圖所示,在異步調(diào)用方式下,調(diào)用方不會(huì)被阻塞,函數(shù)調(diào)用完成后可以立即執(zhí)行接下來(lái)的程序。這時(shí)異步的重點(diǎn)在于調(diào)用方接下來(lái)的程序執(zhí)行可以和文件讀取同時(shí)進(jìn)行。值得注意的是異步調(diào)用對(duì)于程序員來(lái)說(shuō)在理解上是一種負(fù)擔(dān),代碼編寫(xiě)上更是一種負(fù)擔(dān),總的來(lái)說(shuō),上帝在為你打開(kāi)一扇門(mén)的時(shí)候會(huì)適當(dāng)?shù)年P(guān)上一扇窗戶(hù)。

有的同學(xué)可能會(huì)問(wèn),在同步調(diào)用下,調(diào)用方不再繼續(xù)執(zhí)行而是暫停等待,被調(diào)函數(shù)執(zhí)行完后很自然的就是調(diào)用方繼續(xù)執(zhí)行,那么異步調(diào)用下調(diào)用方怎知道被調(diào)函數(shù)是否執(zhí)行完成呢?這就分為調(diào)用方根本就不關(guān)心執(zhí)行結(jié)果和調(diào)用方需要知道執(zhí)行結(jié)果兩種情況。

第一種情況比較簡(jiǎn)單,無(wú)需討論。

第二種情況下就比較有趣了,通常有兩種實(shí)現(xiàn)方式:

1、通知機(jī)制

當(dāng)任務(wù)執(zhí)行完成后發(fā)送信號(hào)來(lái)通知調(diào)用方任務(wù)完成(這里的信號(hào)有很多實(shí)現(xiàn)方式:Linux中的signal,或使用信號(hào)量等機(jī)制都可實(shí)現(xiàn));

2、回調(diào)機(jī)制:
也就是常說(shuō)的callback。

四、具體的編程例子中理解同步和異步

以常見(jiàn)Web服務(wù)為例來(lái)說(shuō)明這個(gè)問(wèn)題。一般來(lái)說(shuō)Web Server接收到用戶(hù)請(qǐng)求后會(huì)有一些典型的處理邏輯,最常見(jiàn)的就是數(shù)據(jù)庫(kù)查詢(xún)(當(dāng)然,你也可以把這里的數(shù)據(jù)庫(kù)查詢(xún)換成其它I/O操作,比如磁盤(pán)讀取、網(wǎng)絡(luò)通信等),在這里假定處理一次用戶(hù)請(qǐng)求需要經(jīng)過(guò)步驟A、B、C,然后讀取數(shù)據(jù)庫(kù),數(shù)據(jù)庫(kù)讀取完成后需要經(jīng)過(guò)步驟D、E、F。

其中步驟A、B、C和D、E、F不需要任何I/O,也就是說(shuō)這六個(gè)步驟不需要讀取文件、網(wǎng)絡(luò)通信等,涉及到I/O操作的只有數(shù)據(jù)庫(kù)查詢(xún)這一步。一般來(lái)說(shuō)Web Server有主線(xiàn)程和數(shù)據(jù)庫(kù)處理線(xiàn)程兩個(gè)典型的線(xiàn)程。

首先我們來(lái)看下最簡(jiǎn)單的實(shí)現(xiàn)方式,也就是同步。

這種方式最為自然也最為容易理解:

主線(xiàn)程在發(fā)出數(shù)據(jù)庫(kù)查詢(xún)請(qǐng)求后就會(huì)被阻塞而暫停運(yùn)行,直到數(shù)據(jù)庫(kù)查詢(xún)完畢后面的D、E、F才可以繼續(xù)運(yùn)行,這就是最為典型的同步方法。

poYBAGPAt4KAVIn6AABIlNCcQO8682.png

如上圖所示,主線(xiàn)程中會(huì)有“空隙”,這個(gè)空隙就是主線(xiàn)程的“休閑時(shí)光”,主線(xiàn)程在這段休閑時(shí)光中需要等待數(shù)據(jù)庫(kù)查詢(xún)完成才能繼續(xù)后續(xù)處理流程。在這里主線(xiàn)程就好比監(jiān)工的老板,數(shù)據(jù)庫(kù)線(xiàn)程就好比苦逼搬磚的程序員,在搬完磚前老板什么都不做只是緊緊的盯著你,等你搬完磚后才去忙其它事情。

1、異步情況:主線(xiàn)程不關(guān)心數(shù)據(jù)庫(kù)操作結(jié)果

如下圖所示,主線(xiàn)程根本就不關(guān)心數(shù)據(jù)庫(kù)是否查詢(xún)完畢,數(shù)據(jù)庫(kù)查詢(xún)完畢后自行處理接下來(lái)的D、E、F三個(gè)步驟。

pYYBAGPAt4KAZ_59AABSS61GMSE289.png

一個(gè)請(qǐng)求通常需要經(jīng)過(guò)七個(gè)步驟,其中前三個(gè)是在主線(xiàn)程中完成的,后四個(gè)是在數(shù)據(jù)庫(kù)線(xiàn)程中完成的,數(shù)據(jù)庫(kù)線(xiàn)程通過(guò)回調(diào)函數(shù)查完數(shù)據(jù)庫(kù)后處理D、E、F幾個(gè)步驟。

偽碼如下:

主線(xiàn)程處理請(qǐng)求和數(shù)據(jù)庫(kù)處理查詢(xún)請(qǐng)求可以同時(shí)進(jìn)行,從系統(tǒng)性能上看能更加充分的利用系統(tǒng)資源,更加快速的處理請(qǐng)求;從用戶(hù)的角度看,系統(tǒng)的響應(yīng)也會(huì)更加迅速。這就是異步的高效之處。但可以看出,異步編程并不如同步來(lái)的容易理解,系統(tǒng)可維護(hù)性上也不如同步模式。

2、異步情況:主線(xiàn)程關(guān)心數(shù)據(jù)庫(kù)操作結(jié)果

如下圖所示,數(shù)據(jù)庫(kù)線(xiàn)程需要將查詢(xún)結(jié)果利用通知機(jī)制發(fā)送給主線(xiàn)程,主線(xiàn)程在接收到消息后繼續(xù)處理上一個(gè)請(qǐng)求的后半部分。

pYYBAGPAt4KAe_GoAABLC9RwSqk895.png

由此我們可以看到:ABCDEF幾個(gè)步驟全部在主線(xiàn)中處理,同時(shí)主線(xiàn)程同樣也沒(méi)有了“休閑時(shí)光”,只不過(guò)在這種情況下數(shù)據(jù)庫(kù)線(xiàn)程是比較清閑的,從這里并沒(méi)有上一種方法高效,但是依然要比同步模式下要高效。但是要注意的是并不是所有的情況下異步都一定比同步高效,還需要結(jié)合具體業(yè)務(wù)以及IO的復(fù)雜度具體情況具體分析。

高并發(fā)中的協(xié)程

協(xié)程是高性能高并發(fā)編程中不可或缺的技術(shù),包括即時(shí)通訊(IM系統(tǒng))在內(nèi)的互聯(lián)網(wǎng)產(chǎn)品應(yīng)用產(chǎn)品中應(yīng)用廣泛,比如號(hào)稱(chēng)支撐微信海量用戶(hù)的后臺(tái)框架就是基于協(xié)程打造的。而且越來(lái)越多的現(xiàn)代編程語(yǔ)言都將協(xié)程視為最重要的語(yǔ)言技術(shù)特征,已知的包括:Go、Python、Kotlin等。

一、從普通函數(shù)到協(xié)程

普通函數(shù)下,只有當(dāng)執(zhí)行完print("c")這句話(huà)后函數(shù)才會(huì)返回,但是在協(xié)程下當(dāng)執(zhí)行完print("a")后func就會(huì)因“暫停并返回”這段代碼返回到調(diào)用函數(shù)。

我寫(xiě)一個(gè)return也能返回,就像這樣:

直接寫(xiě)一個(gè)return語(yǔ)句確實(shí)也能返回,但這樣寫(xiě)的話(huà)return后面的代碼都不會(huì)被執(zhí)行到了。

協(xié)程之所以神奇就神奇在當(dāng)我們從協(xié)程返回后還能繼續(xù)調(diào)用該協(xié)程,并且是從該協(xié)程的上一個(gè)返回點(diǎn)后繼續(xù)執(zhí)行。

就好比孫悟空說(shuō)一聲“定”,函數(shù)就被暫停了:

這時(shí)我們就可以返回到調(diào)用函數(shù),當(dāng)調(diào)用函數(shù)什么時(shí)候想起該協(xié)程后可以再次調(diào)用該協(xié)程,該協(xié)程會(huì)從上一個(gè)返回點(diǎn)繼續(xù)執(zhí)行。值得注意的是當(dāng)普通函數(shù)返回后,進(jìn)程的地址空間中不會(huì)再保存該函數(shù)運(yùn)行時(shí)的任何信息,而協(xié)程返回后,函數(shù)的運(yùn)行時(shí)信息是需要保存下來(lái)的。

二、“Talk is cheap,show me the code”

在python語(yǔ)言中,這個(gè)“定”字同樣使用關(guān)鍵詞yield。

這樣我們的func函數(shù)就變成了:

這時(shí)我們的func就不再是簡(jiǎn)簡(jiǎn)單單的函數(shù)了,而是升級(jí)成為了協(xié)程,那么我們?cè)撛趺词褂媚兀?/p>

很簡(jiǎn)單:

雖然func函數(shù)沒(méi)有return語(yǔ)句,也就是說(shuō)雖然沒(méi)有返回任何值,但是我們依然可以寫(xiě)co = func()這樣的代碼,意思是說(shuō)co就是拿到的協(xié)程了。

接下來(lái)調(diào)用該協(xié)程,使用next(co),運(yùn)行函數(shù)A看看執(zhí)行到第3行的結(jié)果是什么:

顯然,和預(yù)期一樣協(xié)程func在print("a")后因執(zhí)行yield而暫停并返回函數(shù)A。

接下來(lái)是第4行,這個(gè)毫無(wú)疑問(wèn),A函數(shù)在做一些自己的事情,因此會(huì)打?。?/p>

接下來(lái)是重點(diǎn)的一行,當(dāng)執(zhí)行第5行再次調(diào)用協(xié)程時(shí)該打印什么呢?

如果func是普通函數(shù),那么會(huì)執(zhí)行func的第一行代碼,也就是打印a。

但func不是普通函數(shù),而是協(xié)程,我們之前說(shuō)過(guò),協(xié)程會(huì)在上一個(gè)返回點(diǎn)繼續(xù)運(yùn)行,因此這里應(yīng)該執(zhí)行的是func函數(shù)第一個(gè)yield之后的代碼,也就是 print("b")。

三、圖形化解釋

為了更加徹底的理解協(xié)程,我們使用圖形化的方式再看一遍。

首先是普通的函數(shù)調(diào)用:

poYBAGPAt4GAHcnSAAA5V9L5aqU699.png

在該圖中方框內(nèi)表示該函數(shù)的指令序列,如果該函數(shù)不調(diào)用任何其它函數(shù),那么應(yīng)該從上到下依次執(zhí)行,但函數(shù)中可以調(diào)用其它函數(shù),因此其執(zhí)行并不是簡(jiǎn)單的從上到下,箭頭線(xiàn)表示執(zhí)行流的方向。

從上圖中可以看到:首先來(lái)到funcA函數(shù),執(zhí)行一段時(shí)間后發(fā)現(xiàn)調(diào)用了另一個(gè)函數(shù)funcB,這時(shí)控制轉(zhuǎn)移到該函數(shù),執(zhí)行完成后回到main函數(shù)的調(diào)用點(diǎn)繼續(xù)執(zhí)行。這是普通的函數(shù)調(diào)用。

接下來(lái)是協(xié)程:

poYBAGPAt4KAVN7eAAA7ORDX0a4669.png

在這里依然首先在funcA函數(shù)中執(zhí)行,運(yùn)行一段時(shí)間后調(diào)用協(xié)程,協(xié)程開(kāi)始執(zhí)行,直到第一個(gè)掛起點(diǎn),此后就像普通函數(shù)一樣返回funcA函數(shù),funcA函數(shù)執(zhí)行一些代碼后再次調(diào)用該協(xié)程。

三、函數(shù)只是協(xié)程的一種特例

和普通函數(shù)不同的是,協(xié)程能知道自己上一次執(zhí)行到了哪里。協(xié)程會(huì)在函數(shù)被暫停運(yùn)行時(shí)保存函數(shù)的運(yùn)行狀態(tài),并可以從保存的狀態(tài)中恢復(fù)并繼續(xù)運(yùn)行。

四、協(xié)程的歷史

協(xié)程這種概念早在1958年就已經(jīng)提出來(lái)了,要知道這時(shí)線(xiàn)程的概念都還沒(méi)有提出來(lái)。到了1972年,終于有編程語(yǔ)言實(shí)現(xiàn)了這個(gè)概念,這兩門(mén)編程語(yǔ)言就是Simula 67 以及Scheme。但協(xié)程這個(gè)概念始終沒(méi)有流行起來(lái),甚至在1993年還有人考古一樣專(zhuān)門(mén)寫(xiě)論文挖出協(xié)程這種古老的技術(shù)。

因?yàn)檫@一時(shí)期還沒(méi)有線(xiàn)程,如果你想在操作系統(tǒng)寫(xiě)出并發(fā)程序那么你將不得不使用類(lèi)似協(xié)程這樣的技術(shù),后來(lái)線(xiàn)程開(kāi)始出現(xiàn),操作系統(tǒng)終于開(kāi)始原生支持程序的并發(fā)執(zhí)行,就這樣,協(xié)程逐漸淡出了程序員的視線(xiàn)。 直到近些年,隨著互聯(lián)網(wǎng)的發(fā)展,尤其是移動(dòng)互聯(lián)網(wǎng)時(shí)代的到來(lái),服務(wù)端對(duì)高并發(fā)的要求越來(lái)越高,協(xié)程再一次重回技術(shù)主流,各大編程語(yǔ)言都已經(jīng)支持或計(jì)劃開(kāi)始支持協(xié)程。

五、協(xié)程到底如何實(shí)現(xiàn)?

讓我們從問(wèn)題的本質(zhì)出發(fā)來(lái)思考這個(gè)問(wèn)題協(xié)程的本質(zhì)是什么呢? 協(xié)程之所以可以被暫停也可以繼續(xù),那么一定要記錄下被暫停時(shí)的狀態(tài),也就是上下文,當(dāng)繼續(xù)運(yùn)行的時(shí)候要恢復(fù)其上下文(狀態(tài))函數(shù)運(yùn)行時(shí)所有的狀態(tài)信息都位于函數(shù)運(yùn)行時(shí)棧中。 如下圖所示,函數(shù)運(yùn)行時(shí)棧就是需要保存的狀態(tài),也就是所謂的上下文。

poYBAGPAt4KAFG-pAABYgbaix_Y673.png

從上圖中可以看出,該進(jìn)程中只有一個(gè)線(xiàn)程,棧區(qū)中有四個(gè)棧幀,main函數(shù)調(diào)用A函數(shù),A函數(shù)調(diào)用B函數(shù),B函數(shù)調(diào)用C函數(shù),當(dāng)C函數(shù)在運(yùn)行時(shí)整個(gè)進(jìn)程的狀態(tài)就如圖所示。

再仔細(xì)想一想,為什么我們要這么麻煩的來(lái)回copy數(shù)據(jù)呢? 我們需要做的是直接把協(xié)程的運(yùn)行需要的棧幀空間直接開(kāi)辟在堆區(qū)中,這樣都不用來(lái)回copy數(shù)據(jù)了,如下圖所示。

poYBAGPAt4KARW4ZAABtjwx0dhQ451.png

從上圖中可以看到該程序中開(kāi)啟了兩個(gè)協(xié)程,這兩個(gè)協(xié)程的棧區(qū)都是在堆上分配的,這樣我們就可以隨時(shí)中斷或者恢復(fù)協(xié)程的執(zhí)行了。 進(jìn)程地址空間最上層的棧區(qū)現(xiàn)在的作用是用來(lái)保存函數(shù)棧幀的,只不過(guò)這些函數(shù)并不是運(yùn)行在協(xié)程而是普通線(xiàn)程中的。

在上圖中實(shí)際上共有一個(gè)普通線(xiàn)程和兩個(gè)協(xié)程3個(gè)執(zhí)行流。 雖然有3個(gè)執(zhí)行流但我們創(chuàng)建了幾個(gè)線(xiàn)程呢? 答案是:一個(gè)線(xiàn)程。

使用協(xié)程理論上我們可以開(kāi)啟無(wú)數(shù)并發(fā)執(zhí)行流,只要堆區(qū)空間足夠,同時(shí)還沒(méi)有創(chuàng)建線(xiàn)程的開(kāi)銷(xiāo),所有協(xié)程的調(diào)度、切換都發(fā)生在用戶(hù)態(tài),這就是為什么協(xié)程也被稱(chēng)作用戶(hù)態(tài)線(xiàn)程的原因所在。 所以即使創(chuàng)建了N多協(xié)程,但在操作系統(tǒng)看來(lái)依然只有一個(gè)線(xiàn)程,也就是說(shuō)協(xié)程對(duì)操作系統(tǒng)來(lái)說(shuō)是不可見(jiàn)的。

這也許是為什么協(xié)程這個(gè)概念比線(xiàn)程提出的要早的原因,可能是寫(xiě)普通應(yīng)用的程序員比寫(xiě)操作系統(tǒng)的程序員最先遇到需要多個(gè)并行流的需求,那時(shí)可能都還沒(méi)有操作系統(tǒng)的概念,或者操作系統(tǒng)沒(méi)有并行這種需求,所以非操作系統(tǒng)程序員只能自己動(dòng)手實(shí)現(xiàn)執(zhí)行流,也就是協(xié)程。

六、協(xié)程技術(shù)概念小結(jié)

1、協(xié)程是比線(xiàn)程更小的執(zhí)行單元

協(xié)程是比線(xiàn)程更小的一種執(zhí)行單元可以認(rèn)為是輕量級(jí)的線(xiàn)程。 之所以說(shuō)輕的其中一方面的原因是協(xié)程所持有的棧比線(xiàn)程要小很多,java當(dāng)中會(huì)為每個(gè)線(xiàn)程分配1M左右的棧空間,而協(xié)程可能只有幾十或者幾百K,棧主要用來(lái)保存函數(shù)參數(shù)、局部變量和返回地址等信息。

我們知道而線(xiàn)程的調(diào)度是在操作系統(tǒng)中進(jìn)行的,而協(xié)程調(diào)度則是在用戶(hù)空間進(jìn)行的,是開(kāi)發(fā)人員通過(guò)調(diào)用系統(tǒng)底層的執(zhí)行上下文相關(guān)api來(lái)完成的。 有些語(yǔ)言,比如nodejs、go在語(yǔ)言層面支持了協(xié)程,而有些語(yǔ)言,比如C,需要使用第三方庫(kù)才可以擁有協(xié)程的能力。

由于線(xiàn)程是操作系統(tǒng)的最小執(zhí)行單元,因此也可以得出,協(xié)程是基于線(xiàn)程實(shí)現(xiàn)的,協(xié)程的創(chuàng)建、切換、銷(xiāo)毀都是在某個(gè)線(xiàn)程中來(lái)進(jìn)行的。 使用協(xié)程是因?yàn)榫€(xiàn)程的切換成本比較高,而協(xié)程在這方面很有優(yōu)勢(shì)。

2、協(xié)程的切換到底為什么很廉價(jià)?

關(guān)于這個(gè)問(wèn)題,回顧一下線(xiàn)程切換的過(guò)程:

1)線(xiàn)程在進(jìn)行切換的時(shí)候,需要將CPU中的寄存器的信息存儲(chǔ)起來(lái),然后讀入另外一個(gè)線(xiàn)程的數(shù)據(jù),這個(gè)會(huì)花費(fèi)一些時(shí)間;

2)CPU的高速緩存中的數(shù)據(jù),也可能失效,需要重新加載;

3)線(xiàn)程的切換會(huì)涉及到用戶(hù)模式到內(nèi)核模式的切換,據(jù)說(shuō)每次模式切換都需要執(zhí)行上千條指令,很耗時(shí)。

實(shí)際上協(xié)程的切換之所以快的原因主要是:

1)在切換的時(shí)候,寄存器需要保存和加載的數(shù)據(jù)量比較??;

2)高速緩存可以有效利用;

3)沒(méi)有用戶(hù)模式到內(nèi)核模式的切換操作;

4)更有效率的調(diào)度,因?yàn)閰f(xié)程是非搶占式的,前一個(gè)協(xié)程執(zhí)行完畢或者堵塞,才會(huì)讓出CPU,而線(xiàn)程則一般使用了時(shí)間片的算法,會(huì)進(jìn)行很多沒(méi)有必要的切換。

高性能服務(wù)器到底是如何實(shí)現(xiàn)的?

當(dāng)你在閱讀文章的時(shí)候,有沒(méi)有想過(guò),服務(wù)器是怎么把這篇文章發(fā)送給你的呢? 說(shuō)起來(lái)很簡(jiǎn)單不就是一個(gè)用戶(hù)請(qǐng)求嗎? 服務(wù)器根據(jù)請(qǐng)求從數(shù)據(jù)庫(kù)中撈出這篇文章,然后通過(guò)網(wǎng)絡(luò)發(fā)回去嗎。 其實(shí)有點(diǎn)復(fù)雜服務(wù)器端到底是如何并行處理成千上萬(wàn)個(gè)用戶(hù)請(qǐng)求的呢? 這里面又涉及到哪些技術(shù)呢?

一、多進(jìn)程

歷史上最早出現(xiàn)也是最簡(jiǎn)單的一種并行處理多個(gè)請(qǐng)求的方法就是利用多進(jìn)程。 比如在Linux世界中,可以使用fork、exec等系統(tǒng)調(diào)用創(chuàng)建多個(gè)進(jìn)程,可以在父進(jìn)程中接收用戶(hù)的連接請(qǐng)求,然后創(chuàng)建子進(jìn)程去處理用戶(hù)請(qǐng)求。

poYBAGPAt4KAZBrqAABhEzsCA2E404.png

1、多進(jìn)程并行處理的優(yōu)點(diǎn)

1)編程簡(jiǎn)單,非常容易理解;

2)由于各個(gè)進(jìn)程的地址空間是相互隔離的,因此一個(gè)進(jìn)程崩潰后并不會(huì)影響其它進(jìn)程;

3)充分利用多核資源。

2、多進(jìn)程并行處理的缺點(diǎn)

1)各個(gè)進(jìn)程地址空間相互隔離,這一優(yōu)點(diǎn)也會(huì)變成缺點(diǎn),那就是進(jìn)程間要想通信就會(huì)變得比較困難,你需要借助進(jìn)程間通信機(jī)制,想一想你現(xiàn)在知道哪些進(jìn)程間通信機(jī)制,然后讓你用代碼實(shí)現(xiàn)呢? 顯然,進(jìn)程間通信編程相對(duì)復(fù)雜,而且性能也是一大問(wèn)題;

2)創(chuàng)建進(jìn)程開(kāi)銷(xiāo)是比線(xiàn)程要大的,頻繁的創(chuàng)建銷(xiāo)毀進(jìn)程無(wú)疑會(huì)加重系統(tǒng)負(fù)擔(dān)。

二、多線(xiàn)程

由于線(xiàn)程共享進(jìn)程地址空間,因此線(xiàn)程間通信天然不需要借助任何通信機(jī)制,直接讀取內(nèi)存就好了。 線(xiàn)程創(chuàng)建銷(xiāo)毀的開(kāi)銷(xiāo)也變小了,要知道線(xiàn)程就像寄居蟹一樣,房子(地址空間)都是進(jìn)程的,自己只是一個(gè)租客,因此非常的輕量級(jí),創(chuàng)建銷(xiāo)毀的開(kāi)銷(xiāo)也非常小。

我們可以為每個(gè)請(qǐng)求創(chuàng)建一個(gè)線(xiàn)程,即使一個(gè)線(xiàn)程因執(zhí)行I/O操作——比如讀取數(shù)據(jù)庫(kù)等——被阻塞暫停運(yùn)行也不會(huì)影響到其它線(xiàn)程。

pYYBAGPAt4KACXKKAABV6BFiBUo257.png

由于線(xiàn)程共享進(jìn)程地址空間,這在為線(xiàn)程間通信帶來(lái)便利的同時(shí)也帶來(lái)了無(wú)盡的麻煩。 正是由于線(xiàn)程間共享地址空間,因此一個(gè)線(xiàn)程崩潰會(huì)導(dǎo)致整個(gè)進(jìn)程崩潰退出,同時(shí)線(xiàn)程間通信簡(jiǎn)直太簡(jiǎn)單了,簡(jiǎn)單到線(xiàn)程間通信只需要直接讀取內(nèi)存就可以了,也簡(jiǎn)單到出現(xiàn)問(wèn)題也極其容易,死鎖、線(xiàn)程間的同步互斥、等等,這些極容易產(chǎn)生bug,無(wú)數(shù)程序員寶貴的時(shí)間就有相當(dāng)一部分用來(lái)解決多線(xiàn)程帶來(lái)的無(wú)盡問(wèn)題。

雖然線(xiàn)程也有缺點(diǎn),但是相比多進(jìn)程來(lái)說(shuō),線(xiàn)程更有優(yōu)勢(shì),但想單純的利用多線(xiàn)程就能解決高并發(fā)問(wèn)題也是不切實(shí)際的。因?yàn)殡m然線(xiàn)程創(chuàng)建開(kāi)銷(xiāo)相比進(jìn)程小,但依然也是有開(kāi)銷(xiāo)的,對(duì)于動(dòng)輒數(shù)萬(wàn)數(shù)十萬(wàn)的鏈接的高并發(fā)服務(wù)器來(lái)說(shuō),創(chuàng)建數(shù)萬(wàn)個(gè)線(xiàn)程會(huì)有性能問(wèn)題,這包括內(nèi)存占用、線(xiàn)程間切換,也就是調(diào)度的開(kāi)銷(xiāo)。

三、事件驅(qū)動(dòng):Event Loop

到目前為止,提到“并行”二字就會(huì)想到進(jìn)程、線(xiàn)程。但是并行編程只能依賴(lài)這兩項(xiàng)技術(shù)嗎?并不是這樣的!還有另一項(xiàng)并行技術(shù)廣泛應(yīng)用在GUI編程以及服務(wù)器編程中,這就是近幾年非常流行的事件驅(qū)動(dòng)編程:event-based concurrency。

大家不要覺(jué)得這是一項(xiàng)很難懂的技術(shù),實(shí)際上事件驅(qū)動(dòng)編程原理上非常簡(jiǎn)單。

這一技術(shù)需要兩種原料:

1)event;

2)處理event的函數(shù),這一函數(shù)通常被稱(chēng)為event handler;

pYYBAGPAt4KAF4TgAABIiAGLZ6o299.png

由于對(duì)于網(wǎng)絡(luò)通信服務(wù)器來(lái)說(shuō),處理一個(gè)用戶(hù)請(qǐng)求時(shí)大部分時(shí)間其實(shí)都用在了I/O操作上,像數(shù)據(jù)庫(kù)讀寫(xiě)、文件讀寫(xiě)、網(wǎng)絡(luò)讀寫(xiě)等。當(dāng)一個(gè)請(qǐng)求到來(lái),簡(jiǎn)單處理之后可能就需要查詢(xún)數(shù)據(jù)庫(kù)等I/O操作,我們知道I/O是非常慢的,當(dāng)發(fā)起I/O后我們大可以不用等待該I/O操作完成就可以繼續(xù)處理接下來(lái)的用戶(hù)請(qǐng)求。所以一個(gè)event loop可以同時(shí)處理多個(gè)請(qǐng)求。

pYYBAGPAt4KACuY4AABBxDUB2mo119.png

四、事件來(lái)源:IO多路復(fù)用

IO多路復(fù)用技術(shù)通過(guò)一次監(jiān)控多個(gè)文件描述,當(dāng)某個(gè)“文件”(實(shí)際可能是im網(wǎng)絡(luò)通信中socket)可讀或者可寫(xiě)的時(shí)候我們就能同時(shí)處理多個(gè)文件描述符啦。

這樣IO多路復(fù)用技術(shù)就成了event loop的原材料供應(yīng)商,源源不斷的給我們提供各種event,這樣關(guān)于event來(lái)源的問(wèn)題就解決了。

pYYBAGPAt4KACZQ1AABT4u4xJFw848.png

五、問(wèn)題:阻塞式IO

當(dāng)我們進(jìn)行IO操作,比如讀取文件時(shí),如果文件沒(méi)有讀取完成,那么我們的程序(線(xiàn)程)會(huì)被阻塞而暫停執(zhí)行,這在多線(xiàn)程中不是問(wèn)題,因?yàn)椴僮飨到y(tǒng)還可以調(diào)度其它線(xiàn)程。 但是在單線(xiàn)程的event loop中是有問(wèn)題的,原因就在于當(dāng)我們?cè)趀vent loop中執(zhí)行阻塞式IO操作時(shí)整個(gè)線(xiàn)程(event loop)會(huì)被暫停運(yùn)行,這時(shí)操作系統(tǒng)將沒(méi)有其它線(xiàn)程可以調(diào)度,因?yàn)橄到y(tǒng)中只有一個(gè)event loop在處理用戶(hù)請(qǐng)求,這樣當(dāng)event loop線(xiàn)程被阻塞暫停運(yùn)行時(shí)所有用戶(hù)請(qǐng)求都沒(méi)有辦法被處理。 你能想象當(dāng)服務(wù)器在處理其它用戶(hù)請(qǐng)求讀取數(shù)據(jù)庫(kù)導(dǎo)致你的請(qǐng)求被暫停嗎?

pYYBAGPAt4KAXHabAABMB0i4FqQ484.png

因此:在基于事件驅(qū)動(dòng)編程時(shí)有一條注意事項(xiàng),那就是不允許發(fā)起阻塞式IO。 有的同學(xué)可能會(huì)問(wèn),如果不能發(fā)起阻塞式IO的話(huà),那么該怎樣進(jìn)行IO操作呢?

六、解決方法:非阻塞式IO

為克服阻塞式IO所帶來(lái)的問(wèn)題,現(xiàn)代操作系統(tǒng)開(kāi)始提供一種新的發(fā)起IO請(qǐng)求的方法,這種方法就是異步IO。 對(duì)應(yīng)的,阻塞式IO就是同步IO,關(guān)于同步和異步詳見(jiàn)上文。

異步IO時(shí),假設(shè)調(diào)用aio_read函數(shù)(具體的異步IO API請(qǐng)參考具體的操作系統(tǒng)平臺(tái)),也就是異步讀取,當(dāng)我們調(diào)用該函數(shù)后可以立即返回,并繼續(xù)其它事情,雖然此時(shí)該文件可能還沒(méi)有被讀取,這樣就不會(huì)阻塞調(diào)用線(xiàn)程了。 此外,操作系統(tǒng)還會(huì)提供其它方法供調(diào)用線(xiàn)程來(lái)檢測(cè)IO操作是否完成。

七、基于事件驅(qū)動(dòng)并行編程的難點(diǎn)

雖然有異步IO來(lái)解決event loop可能被阻塞的問(wèn)題,但是基于事件編程依然是困難的。

首先event loop是運(yùn)行在一個(gè)線(xiàn)程中的,顯然一個(gè)線(xiàn)程是沒(méi)有辦法充分利用多核資源的,有的同學(xué)可能會(huì)說(shuō)那就創(chuàng)建多個(gè)event loop實(shí)例不就可以了,這樣就有多個(gè)event loop線(xiàn)程了,但是這樣一來(lái)多線(xiàn)程問(wèn)題又會(huì)出現(xiàn)。

其次在于編程方面,異步編程需要結(jié)合回調(diào)函數(shù)(這種編程方式需要把處理邏輯分為兩部分:一部分調(diào)用方自己處理,另一部分在回調(diào)函數(shù)中處理),這一編程方式的改變加重了程序員在理解上的負(fù)擔(dān),基于事件編程的項(xiàng)目后期會(huì)很難擴(kuò)展以及維護(hù)。

八、更好的方法

有沒(méi)有一種方法既能結(jié)合同步IO的簡(jiǎn)單理解又不會(huì)因同步調(diào)用導(dǎo)致線(xiàn)程被阻塞呢? 答案是肯定的,這就是用戶(hù)態(tài)線(xiàn)程(user level thread),也就是大名鼎鼎的協(xié)程。

雖然基于事件編程有這樣那樣的缺點(diǎn),但是在當(dāng)今的高性能高并發(fā)服務(wù)器上基于事件編程方式依然非常流行,但已經(jīng)不是純粹的基于單一線(xiàn)程的事件驅(qū)動(dòng)了,而是 event loop + multi thread + user level thread。

進(jìn)程、線(xiàn)程、協(xié)程

一、什么是進(jìn)程?

1、基本常識(shí)

計(jì)算機(jī)的核心是CPU,它承擔(dān)了所有的計(jì)算任務(wù); 操作系統(tǒng)是計(jì)算機(jī)的管理者,它負(fù)責(zé)任務(wù)的調(diào)度、資源的分配和管理,統(tǒng)領(lǐng)整個(gè)計(jì)算機(jī)硬件; 應(yīng)用程序則是具有某種功能的程序,程序是運(yùn)行于操作系統(tǒng)之上的。

進(jìn)程是一個(gè)具有一定獨(dú)立功能的程序在一個(gè)數(shù)據(jù)集上的一次動(dòng)態(tài)執(zhí)行的過(guò)程,是操作系統(tǒng)進(jìn)行資源分配和調(diào)度的一個(gè)獨(dú)立單位,是應(yīng)用程序運(yùn)行的載體。 進(jìn)程是一種抽象的概念,從來(lái)沒(méi)有統(tǒng)一的標(biāo)準(zhǔn)定義。

進(jìn)程一般由程序、數(shù)據(jù)集合和進(jìn)程控制塊三部分組成:

程序用于描述進(jìn)程要完成的功能,是控制進(jìn)程執(zhí)行的指令集;

數(shù)據(jù)集合是程序在執(zhí)行時(shí)所需要的數(shù)據(jù)和工作區(qū);

程序控制塊(Program Control Block,簡(jiǎn)稱(chēng)PCB),包含進(jìn)程的描述信息和控制信息,是進(jìn)程存在的唯一標(biāo)志。

進(jìn)程的特點(diǎn):

動(dòng)態(tài)性:進(jìn)程是程序的一次執(zhí)行過(guò)程,是臨時(shí)的,有生命期的,是動(dòng)態(tài)產(chǎn)生,動(dòng)態(tài)消亡的;

并發(fā)性:任何進(jìn)程都可以同其他進(jìn)程一起并發(fā)執(zhí)行;

獨(dú)立性:進(jìn)程是系統(tǒng)進(jìn)行資源分配和調(diào)度的一個(gè)獨(dú)立單位;

結(jié)構(gòu)性:進(jìn)程由程序、數(shù)據(jù)和進(jìn)程控制塊三部分組成。

2、為什么要有多進(jìn)程?

多進(jìn)程目的是提高cpu的使用率。 假設(shè)只有一個(gè)進(jìn)程(先不談多線(xiàn)程),從操作系統(tǒng)的層面看,我們使用打印機(jī)的步驟有如下:

1)使用CPU執(zhí)行程序,去硬盤(pán)讀取需要打印的文件,然后CPU會(huì)長(zhǎng)時(shí)間的等待,直到硬盤(pán)讀寫(xiě)完成;

2)使用CPU執(zhí)行程序,讓打印機(jī)打印這些內(nèi)容,然后CPU會(huì)長(zhǎng)時(shí)間的等待,等待打印結(jié)束。

在這樣的情況下:其實(shí)CPU的使用率其實(shí)非常的低。

打印一個(gè)文件從頭到尾需要的時(shí)間可能是1分鐘,而cpu使用的時(shí)間總和可能加起來(lái)只有幾秒鐘。 而后面如果單進(jìn)程執(zhí)行游戲的程序的時(shí)候,CPU也同樣會(huì)有大量的空閑時(shí)間。

使用多進(jìn)程后:

當(dāng)CPU在等待硬盤(pán)讀寫(xiě)文件,或者在等待打印機(jī)打印的時(shí)候,CPU可以去執(zhí)行游戲的程序,這樣CPU就能盡可能高的提高使用率。

再具體一點(diǎn)說(shuō),其實(shí)也提高了效率。 因?yàn)樵诘却蛴C(jī)的時(shí)候,這時(shí)候顯卡也是閑置的,如果用多進(jìn)程并行的話(huà),游戲進(jìn)程完全可以并行使用顯卡,并且與打印機(jī)之間也不會(huì)互相影響。

3、總結(jié)

進(jìn)程直觀(guān)點(diǎn)說(shuō)是保存在硬盤(pán)上的程序運(yùn)行以后,會(huì)在內(nèi)存空間里形成一個(gè)獨(dú)立的內(nèi)存體,這個(gè)內(nèi)存體有自己獨(dú)立的地址空間,有自己的堆,上級(jí)掛靠單位是操作系統(tǒng)。 操作系統(tǒng)會(huì)進(jìn)程為單位,分配系統(tǒng)資源(CPU時(shí)間片、內(nèi)存等資源),進(jìn)程是資源分配的最小單位。

二、什么是線(xiàn)程?

1、基本常識(shí)

早期操作系統(tǒng)中并沒(méi)有線(xiàn)程的概念,進(jìn)程是能擁有資源和獨(dú)立運(yùn)行的最小單位,也是程序執(zhí)行的最小單位。 任務(wù)調(diào)度采用的是時(shí)間片輪轉(zhuǎn)的搶占式調(diào)度方式,而進(jìn)程是任務(wù)調(diào)度的最小單位,每個(gè)進(jìn)程有各自獨(dú)立的一塊內(nèi)存,使得各個(gè)進(jìn)程之間內(nèi)存地址相互隔離。 后來(lái)隨著計(jì)算機(jī)的發(fā)展,對(duì)CPU的要求越來(lái)越高,進(jìn)程之間的切換開(kāi)銷(xiāo)較大,已經(jīng)無(wú)法滿(mǎn)足越來(lái)越復(fù)雜的程序的要求了。 于是就發(fā)明了線(xiàn)程。

線(xiàn)程是程序執(zhí)行中一個(gè)單一的順序控制流程:

1)程序執(zhí)行流的最小單元

2)處理器調(diào)度和分派的基本單位

一個(gè)進(jìn)程可以有一個(gè)或多個(gè)線(xiàn)程,各個(gè)線(xiàn)程之間共享程序的內(nèi)存空間(也就是所在進(jìn)程的內(nèi)存空間)。 一個(gè)標(biāo)準(zhǔn)的線(xiàn)程由線(xiàn)程ID、當(dāng)前指令指針(PC)、寄存器和堆棧組成。 而進(jìn)程由內(nèi)存空間(代碼、數(shù)據(jù)、進(jìn)程空間、打開(kāi)的文件)和一個(gè)或多個(gè)線(xiàn)程組成。

poYBAGPAt4KAbQWmAAC2YGmaw5k758.png

如上圖所示,在任務(wù)管理器的進(jìn)程一欄里,有道詞典和有道云筆記就是進(jìn)程,而在進(jìn)程下又有著多個(gè)執(zhí)行不同任務(wù)的線(xiàn)程。

2、任務(wù)調(diào)度

線(xiàn)程是什么? 要理解這個(gè)概念,需要先了解一下操作系統(tǒng)的一些相關(guān)概念。 大部分操作系統(tǒng)(如Windows、Linux)的任務(wù)調(diào)度是采用時(shí)間片輪轉(zhuǎn)的搶占式調(diào)度方式。 在一個(gè)進(jìn)程中:當(dāng)一個(gè)線(xiàn)程任務(wù)執(zhí)行幾毫秒后,會(huì)由操作系統(tǒng)的內(nèi)核(負(fù)責(zé)管理各個(gè)任務(wù))進(jìn)行調(diào)度,通過(guò)硬件的計(jì)數(shù)器中斷處理器,讓該線(xiàn)程強(qiáng)制暫停并將該線(xiàn)程的寄存器放入內(nèi)存中,通過(guò)查看線(xiàn)程列表決定接下來(lái)執(zhí)行哪一個(gè)線(xiàn)程,并從內(nèi)存中恢復(fù)該線(xiàn)程的寄存器,最后恢復(fù)該線(xiàn)程的執(zhí)行,從而去執(zhí)行下一個(gè)任務(wù)。

上述過(guò)程中任務(wù)執(zhí)行的那一小段時(shí)間叫做時(shí)間片,任務(wù)正在執(zhí)行時(shí)的狀態(tài)叫運(yùn)行狀態(tài),被暫停的線(xiàn)程任務(wù)狀態(tài)叫做就緒狀態(tài),意為等待下一個(gè)屬于它的時(shí)間片的到來(lái)。

這種方式保證了每個(gè)線(xiàn)程輪流執(zhí)行,由于CPU的執(zhí)行效率非常高,時(shí)間片非常短,在各個(gè)任務(wù)之間快速地切換,給人的感覺(jué)就是多個(gè)任務(wù)在“同時(shí)進(jìn)行”,這也就是我們所說(shuō)的并發(fā)(別覺(jué)得并發(fā)有多高深,它的實(shí)現(xiàn)很復(fù)雜,但它的概念很簡(jiǎn)單,就是一句話(huà):多個(gè)任務(wù)同時(shí)執(zhí)行)。

pYYBAGPAt4KAF1ItAAA5JMFTsa8168.png

3、進(jìn)程與線(xiàn)程的區(qū)別

進(jìn)程與線(xiàn)程的關(guān)系

1)線(xiàn)程是程序執(zhí)行的最小單位,而進(jìn)程是操作系統(tǒng)分配資源的最小單位;

2)一個(gè)進(jìn)程由一個(gè)或多個(gè)線(xiàn)程組成,線(xiàn)程是一個(gè)進(jìn)程中代碼的不同執(zhí)行路線(xiàn);

3)進(jìn)程之間相互獨(dú)立,但同一進(jìn)程下的各個(gè)線(xiàn)程之間共享程序的內(nèi)存空間(包括代碼段、數(shù)據(jù)集、堆等)及一些進(jìn)程級(jí)的資源(如打開(kāi)文件和信號(hào)),某進(jìn)程內(nèi)的線(xiàn)程在其它進(jìn)程不可見(jiàn);

4)線(xiàn)程上下文切換比進(jìn)程上下文切換要快得多。

poYBAGPAt4KAFdIVAABW0Jc_sM8711.png

▲ 進(jìn)程與線(xiàn)程的資源共享關(guān)系

pYYBAGPAt4KAA4pgAAA5SpLkwC8623.png

▲ 單線(xiàn)程與多線(xiàn)程的關(guān)系

總之線(xiàn)程和進(jìn)程都是一種抽象的概念,線(xiàn)程是一種比進(jìn)程更小的抽象,線(xiàn)程和進(jìn)程都可用于實(shí)現(xiàn)并發(fā)。

在早期的操作系統(tǒng)中并沒(méi)有線(xiàn)程的概念,進(jìn)程是能擁有資源和獨(dú)立運(yùn)行的最小單位,也是程序執(zhí)行的最小單位。 它相當(dāng)于一個(gè)進(jìn)程里只有一個(gè)線(xiàn)程,進(jìn)程本身就是線(xiàn)程。 所以線(xiàn)程有時(shí)被稱(chēng)為輕量級(jí)進(jìn)程。

后來(lái)隨著計(jì)算機(jī)的發(fā)展,對(duì)多個(gè)任務(wù)之間上下文切換的效率要求越來(lái)越高,就抽象出一個(gè)更小的概念——線(xiàn)程,一般一個(gè)進(jìn)程會(huì)有多個(gè)(也可以是一個(gè))線(xiàn)程。

pYYBAGPAt4KAAipnAAA-iS2yQbU128.png

4、多線(xiàn)程與多核

上面提到的時(shí)間片輪轉(zhuǎn)的調(diào)度方式說(shuō)一個(gè)任務(wù)執(zhí)行一小段時(shí)間后強(qiáng)制暫停去執(zhí)行下一個(gè)任務(wù),每個(gè)任務(wù)輪流執(zhí)行。很多操作系統(tǒng)的書(shū)都說(shuō)“同一時(shí)間點(diǎn)只有一個(gè)任務(wù)在執(zhí)行”。其實(shí)“同一時(shí)間點(diǎn)只有一個(gè)任務(wù)在執(zhí)行”這句話(huà)是不準(zhǔn)確的,至少它是不全面的。那多核處理器的情況下,線(xiàn)程是怎樣執(zhí)行呢?這就需要了解內(nèi)核線(xiàn)程。

多核(心)處理器是指在一個(gè)處理器上集成多個(gè)運(yùn)算核心從而提高計(jì)算能力,也就是有多個(gè)真正并行計(jì)算的處理核心,每一個(gè)處理核心對(duì)應(yīng)一個(gè)內(nèi)核線(xiàn)程。內(nèi)核線(xiàn)程(Kernel Thread,KLT)就是直接由操作系統(tǒng)內(nèi)核支持的線(xiàn)程,這種線(xiàn)程由內(nèi)核來(lái)完成線(xiàn)程切換,內(nèi)核通過(guò)操作調(diào)度器對(duì)線(xiàn)程進(jìn)行調(diào)度,并負(fù)責(zé)將線(xiàn)程的任務(wù)映射到各個(gè)處理器上。

一般一個(gè)處理核心對(duì)應(yīng)一個(gè)內(nèi)核線(xiàn)程,比如單核處理器對(duì)應(yīng)一個(gè)內(nèi)核線(xiàn)程,雙核處理器對(duì)應(yīng)兩個(gè)內(nèi)核線(xiàn)程,四核處理器對(duì)應(yīng)四個(gè)內(nèi)核線(xiàn)程。

現(xiàn)在的電腦一般是雙核四線(xiàn)程、四核八線(xiàn)程,是采用超線(xiàn)程技術(shù)將一個(gè)物理處理核心模擬成兩個(gè)邏輯處理核心,對(duì)應(yīng)兩個(gè)內(nèi)核線(xiàn)程,所以在操作系統(tǒng)中看到的CPU數(shù)量是實(shí)際物理CPU數(shù)量的兩倍,如你的電腦是雙核四線(xiàn)程,打開(kāi)“任務(wù)管理器 -> 性能”可以看到4個(gè)CPU的監(jiān)視器,四核八線(xiàn)程可以看到8個(gè)CPU的監(jiān)視器。

超線(xiàn)程技術(shù)就是利用特殊的硬件指令,把一個(gè)物理芯片模擬成兩個(gè)邏輯處理核心,讓單個(gè)處理器都能使用線(xiàn)程級(jí)并行計(jì)算,進(jìn)而兼容多線(xiàn)程操作系統(tǒng)和軟件,減少了CPU的閑置時(shí)間,提高的CPU的運(yùn)行效率。這種超線(xiàn)程技術(shù)(如雙核四線(xiàn)程)由處理器硬件的決定,同時(shí)也需要操作系統(tǒng)的支持才能在計(jì)算機(jī)中表現(xiàn)出來(lái)。

程序一般不會(huì)直接去使用內(nèi)核線(xiàn)程,而是去使用內(nèi)核線(xiàn)程的一種高級(jí)接口——輕量級(jí)進(jìn)程(Lightweight Process,LWP),輕量級(jí)進(jìn)程就是通常意義上所講的線(xiàn)程,也被叫做用戶(hù)線(xiàn)程。

由于每個(gè)輕量級(jí)進(jìn)程都由一個(gè)內(nèi)核線(xiàn)程支持,因此只有先支持內(nèi)核線(xiàn)程,才能有輕量級(jí)進(jìn)程。

用戶(hù)線(xiàn)程與內(nèi)核線(xiàn)程的對(duì)應(yīng)關(guān)系有三種模型:

1)一對(duì)一模型;

2)多對(duì)一模型;

3)多對(duì)多模型。

5、一對(duì)一模型

對(duì)于一對(duì)一模型來(lái)說(shuō):一個(gè)用戶(hù)線(xiàn)程就唯一地對(duì)應(yīng)一個(gè)內(nèi)核線(xiàn)程(反過(guò)來(lái)不一定成立,一個(gè)內(nèi)核線(xiàn)程不一定有對(duì)應(yīng)的用戶(hù)線(xiàn)程)。 這樣如果CPU沒(méi)有采用超線(xiàn)程技術(shù)(如四核四線(xiàn)程的計(jì)算機(jī)),一個(gè)用戶(hù)線(xiàn)程就唯一地映射到一個(gè)物理CPU的內(nèi)核線(xiàn)程,線(xiàn)程之間的并發(fā)是真正的并發(fā)。

一對(duì)一模型優(yōu)點(diǎn)

使用戶(hù)線(xiàn)程具有與內(nèi)核線(xiàn)程一樣的優(yōu)點(diǎn)一個(gè)線(xiàn)程因某種原因阻塞時(shí)其他線(xiàn)程的執(zhí)行不受影響(此處,一對(duì)一模型也可以讓多線(xiàn)程程序在多處理器的系統(tǒng)上有更好的表現(xiàn))。

一對(duì)一模型缺點(diǎn)

1)許多操作系統(tǒng)限制了內(nèi)核線(xiàn)程的數(shù)量,因此一對(duì)一模型會(huì)使用戶(hù)線(xiàn)程的數(shù)量受到限制;

2)許多操作系統(tǒng)內(nèi)核線(xiàn)程調(diào)度時(shí),上下文切換的開(kāi)銷(xiāo)較大,導(dǎo)致用戶(hù)線(xiàn)程的執(zhí)行效率下降。

pYYBAGPAt4GANakzAAAu3ARYrWg246.png

▲ 一對(duì)一模型

6、多對(duì)一模型

多對(duì)一模型將多個(gè)用戶(hù)線(xiàn)程映射到一個(gè)內(nèi)核線(xiàn)程上,線(xiàn)程之間的切換由用戶(hù)態(tài)的代碼來(lái)進(jìn)行,系統(tǒng)內(nèi)核感受不到線(xiàn)程的實(shí)現(xiàn)方式。 用戶(hù)線(xiàn)程的建立、同步、銷(xiāo)毀等都在用戶(hù)態(tài)中完成,不需要內(nèi)核的介入。

多對(duì)一模型優(yōu)點(diǎn)

1)多對(duì)一模型的線(xiàn)程上下文切換速度要快許多;

2)多對(duì)一模型對(duì)用戶(hù)線(xiàn)程的數(shù)量幾乎無(wú)限制。

多對(duì)一模型缺點(diǎn)

1)如果其中一個(gè)用戶(hù)線(xiàn)程阻塞,那么其它所有線(xiàn)程都將無(wú)法執(zhí)行,因?yàn)榇藭r(shí)內(nèi)核線(xiàn)程也隨之阻塞了;

2)在多處理器系統(tǒng)上,處理器數(shù)量的增加對(duì)多對(duì)一模型的線(xiàn)程性能不會(huì)有明顯的增加,因?yàn)樗械挠脩?hù)線(xiàn)程都映射到一個(gè)處理器上了。

poYBAGPAt4KAbJf3AAA0rnsT-Rk203.png

▲ 多對(duì)一模型

7、多對(duì)多模型

多對(duì)多模型結(jié)合了一對(duì)一模型和多對(duì)一模型的優(yōu)點(diǎn)將多個(gè)用戶(hù)線(xiàn)程映射到多個(gè)內(nèi)核線(xiàn)程上,由線(xiàn)程庫(kù)負(fù)責(zé)在可用的可調(diào)度實(shí)體上調(diào)度用戶(hù)線(xiàn)程。

這使得線(xiàn)程的上下文切換非??欤?yàn)樗苊饬讼到y(tǒng)調(diào)用。 但是增加了復(fù)雜性和優(yōu)先級(jí)倒置的可能性,以及在用戶(hù)態(tài)調(diào)度程序和內(nèi)核調(diào)度程序之間沒(méi)有廣泛(且高昂)協(xié)調(diào)的次優(yōu)調(diào)度。

多對(duì)多模型的優(yōu)點(diǎn)

1)一個(gè)用戶(hù)線(xiàn)程的阻塞不會(huì)導(dǎo)致所有線(xiàn)程的阻塞,因?yàn)榇藭r(shí)還有別的內(nèi)核線(xiàn)程被調(diào)度來(lái)執(zhí)行;

2)多對(duì)多模型對(duì)用戶(hù)線(xiàn)程的數(shù)量沒(méi)有限制;

3)在多處理器的操作系統(tǒng)中,多對(duì)多模型的線(xiàn)程也能得到一定的性能提升,但提升的幅度不如一對(duì)一模型的高。

poYBAGPAt4KAIE-hAAA9JueYCwg303.png

▲ 多對(duì)多模型

在現(xiàn)在流行的操作系統(tǒng)中,大都采用多對(duì)多的模型。

8、查看進(jìn)程與線(xiàn)程

一個(gè)應(yīng)用程序可能是多線(xiàn)程的,也可能是多進(jìn)程的,如何查看呢?

在Windows下我們只須打開(kāi)任務(wù)管理器就能查看一個(gè)應(yīng)用程序的進(jìn)程和線(xiàn)程數(shù)。 按“Ctrl+Alt+Del”或右鍵快捷工具欄打開(kāi)任務(wù)管理器。

在“進(jìn)程”選項(xiàng)卡下,我們可以看到一個(gè)應(yīng)用程序包含的線(xiàn)程數(shù)。

如果一個(gè)應(yīng)用程序有多個(gè)進(jìn)程,我們能看到每一個(gè)進(jìn)程,如在上圖中,Google的Chrome瀏覽器就有多個(gè)進(jìn)程。

同時(shí),如果打開(kāi)了一個(gè)應(yīng)用程序的多個(gè)實(shí)例也會(huì)有多個(gè)進(jìn)程,如上圖中我打開(kāi)了兩個(gè)cmd窗口,就有兩個(gè)cmd進(jìn)程。 如果看不到線(xiàn)程數(shù)這一列,可以再點(diǎn)擊“查看 -> 選擇列”菜單,增加監(jiān)聽(tīng)的列。

查看CPU和內(nèi)存的使用率:在性能選項(xiàng)卡中,我們可以查看CPU和內(nèi)存的使用率,根據(jù)CPU使用記錄的監(jiān)視器的個(gè)數(shù)還能看出邏輯處理核心的個(gè)數(shù),如我的雙核四線(xiàn)程的計(jì)算機(jī)就有四個(gè)監(jiān)視器。

pYYBAGPAt4KAX4O7AACNVgB6qWM391.png

▲ 查看CPU和內(nèi)存的使用率

9、線(xiàn)程的生命周期

當(dāng)線(xiàn)程的數(shù)量小于處理器的數(shù)量時(shí),線(xiàn)程的并發(fā)是真正的并發(fā),不同的線(xiàn)程運(yùn)行在不同的處理器上。 但當(dāng)線(xiàn)程的數(shù)量大于處理器的數(shù)量時(shí),線(xiàn)程的并發(fā)會(huì)受到一些阻礙,此時(shí)并不是真正的并發(fā),因?yàn)榇藭r(shí)至少有一個(gè)處理器會(huì)運(yùn)行多個(gè)線(xiàn)程。

在單個(gè)處理器運(yùn)行多個(gè)線(xiàn)程時(shí),并發(fā)是一種模擬出來(lái)的狀態(tài)。 操作系統(tǒng)采用時(shí)間片輪轉(zhuǎn)的方式輪流執(zhí)行每一個(gè)線(xiàn)程。 現(xiàn)在,幾乎所有的現(xiàn)代操作系統(tǒng)采用的都是時(shí)間片輪轉(zhuǎn)的搶占式調(diào)度方式,如我們熟悉的Unix、Linux、Windows及macOS等流行的操作系統(tǒng)。

我們知道線(xiàn)程是程序執(zhí)行的最小單位,也是任務(wù)執(zhí)行的最小單位。 在早期只有進(jìn)程的操作系統(tǒng)中,進(jìn)程有五種狀態(tài),創(chuàng)建、就緒、運(yùn)行、阻塞(等待)、退出。 早期的進(jìn)程相當(dāng)于現(xiàn)在的只有單個(gè)線(xiàn)程的進(jìn)程,那么現(xiàn)在的多線(xiàn)程也有五種狀態(tài),現(xiàn)在的多線(xiàn)程的生命周期與早期進(jìn)程的生命周期類(lèi)似。

poYBAGPAt4KAYDdsAABMI2rJ8e4476.png

▲ 早期進(jìn)程的生命周期

進(jìn)程在運(yùn)行過(guò)程有三種狀態(tài):就緒、運(yùn)行、阻塞,創(chuàng)建和退出狀態(tài)描述的是進(jìn)程的創(chuàng)建過(guò)程和退出過(guò)程。

早期進(jìn)程的生命周期:

創(chuàng)建:進(jìn)程正在創(chuàng)建,還不能運(yùn)行。操作系統(tǒng)在創(chuàng)建進(jìn)程時(shí)要進(jìn)行的工作包括分配和建立進(jìn)程控制塊表項(xiàng)、建立資源表格并分配資源、加載程序并建立地址空間;

就緒:時(shí)間片已用完,此線(xiàn)程被強(qiáng)制暫停,等待下一個(gè)屬于它的時(shí)間片到來(lái);

運(yùn)行:此線(xiàn)程正在執(zhí)行,正在占用時(shí)間片;

阻塞:也叫等待狀態(tài),等待某一事件(如IO或另一個(gè)線(xiàn)程)執(zhí)行完;

退出:進(jìn)程已結(jié)束,所以也稱(chēng)結(jié)束狀態(tài),釋放操作系統(tǒng)分配的資源。

poYBAGPAt4KAGNuKAABEl4ojtsE578.png

▲ 線(xiàn)程的生命周期

線(xiàn)程的生命周期跟進(jìn)程很類(lèi)似:

創(chuàng)建:一個(gè)新的線(xiàn)程被創(chuàng)建,等待該線(xiàn)程被調(diào)用執(zhí)行;

就緒:時(shí)間片已用完,此線(xiàn)程被強(qiáng)制暫停,等待下一個(gè)屬于它的時(shí)間片到來(lái);

運(yùn)行:此線(xiàn)程正在執(zhí)行,正在占用時(shí)間片;

阻塞:也叫等待狀態(tài),等待某一事件(如IO或另一個(gè)線(xiàn)程)執(zhí)行完;

退出:一個(gè)線(xiàn)程完成任務(wù)或者其他終止條件發(fā)生,該線(xiàn)程終止進(jìn)入退出狀態(tài),退出狀態(tài)釋放該線(xiàn)程所分配的資源。

五、什么是協(xié)程?

1、基本常識(shí)

協(xié)程是一種基于線(xiàn)程之上,但又比線(xiàn)程更加輕量級(jí)的存在,這種由程序員自己寫(xiě)程序來(lái)管理的輕量級(jí)線(xiàn)程叫做“用戶(hù)空間線(xiàn)程”,具有對(duì)內(nèi)核來(lái)說(shuō)不可見(jiàn)的特性。由于是自主開(kāi)辟的異步任務(wù),所以很多人也更喜歡叫它們纖程(Fiber),或者綠色線(xiàn)程(GreenThread)。正如一個(gè)進(jìn)程可以擁有多個(gè)線(xiàn)程一樣,一個(gè)線(xiàn)程也可以擁有多個(gè)協(xié)程。

pYYBAGPAt4KAT0m0AADM7xHcYOU441.png

2、協(xié)程的目的

對(duì)于Java程序員來(lái)說(shuō),在傳統(tǒng)的J2EE系統(tǒng)中都是基于每個(gè)請(qǐng)求占用一個(gè)線(xiàn)程去完成完整的業(yè)務(wù)邏輯(包括事務(wù))。所以系統(tǒng)的吞吐能力取決于每個(gè)線(xiàn)程的操作耗時(shí)。

如果遇到很耗時(shí)的I/O行為,則整個(gè)系統(tǒng)的吞吐立刻下降,因?yàn)檫@個(gè)時(shí)候線(xiàn)程一直處于阻塞狀態(tài),如果線(xiàn)程很多的時(shí)候,會(huì)存在很多線(xiàn)程處于空閑狀態(tài)(等待該線(xiàn)程執(zhí)行完才能執(zhí)行),造成了資源應(yīng)用不徹底。

最常見(jiàn)的例子就是JDBC(它是同步阻塞的),這也是為什么很多人都說(shuō)數(shù)據(jù)庫(kù)是瓶頸的原因。這里的耗時(shí)其實(shí)是讓CPU一直在等待I/O返回,說(shuō)白了線(xiàn)程根本沒(méi)有利用CPU去做運(yùn)算,而是處于空轉(zhuǎn)狀態(tài)。而另外過(guò)多的線(xiàn)程,也會(huì)帶來(lái)更多的ContextSwitch開(kāi)銷(xiāo)。

對(duì)于上述問(wèn)題:現(xiàn)階段行業(yè)里的比較流行的解決方案之一就是單線(xiàn)程加上異步回調(diào)。其代表派是 node.js 以及 Java 里的新秀 Vert.x 。

而協(xié)程的目的就是當(dāng)出現(xiàn)長(zhǎng)時(shí)間的I/O操作時(shí),通過(guò)讓出目前的協(xié)程調(diào)度,執(zhí)行下一個(gè)任務(wù)的方式,來(lái)消除ContextSwitch上的開(kāi)銷(xiāo)。

3、協(xié)程的特點(diǎn)

協(xié)程的特點(diǎn)總結(jié)一下就是:

1)線(xiàn)程的切換由操作系統(tǒng)負(fù)責(zé)調(diào)度,協(xié)程由用戶(hù)自己進(jìn)行調(diào)度,因此減少了上下文切換,提高了效率;

2)線(xiàn)程的默認(rèn)Stack大小是1M,而協(xié)程更輕量,接近1K。 因此可以在相同的內(nèi)存中開(kāi)啟更多的協(xié)程;

3)由于在同一個(gè)線(xiàn)程上,因此可以避免競(jìng)爭(zhēng)關(guān)系而使用鎖;

4)適用于被阻塞的,且需要大量并發(fā)的場(chǎng)景。 但不適用于大量計(jì)算的多線(xiàn)程,遇到此種情況,更好實(shí)用線(xiàn)程去解決。

4、協(xié)程的原理

當(dāng)出現(xiàn)IO阻塞的時(shí)候,由協(xié)程的調(diào)度器進(jìn)行調(diào)度,通過(guò)將數(shù)據(jù)流立刻yield掉(主動(dòng)讓出),并且記錄當(dāng)前棧上的數(shù)據(jù),阻塞完后立刻再通過(guò)線(xiàn)程恢復(fù)棧,并把阻塞的結(jié)果放到這個(gè)線(xiàn)程上去跑。

這樣看上去好像跟寫(xiě)同步代碼沒(méi)有任何差別,這整個(gè)流程可以稱(chēng)為coroutine,而跑在由coroutine負(fù)責(zé)調(diào)度的線(xiàn)程稱(chēng)為Fiber。 比如Golang里的 go關(guān)鍵字其實(shí)就是負(fù)責(zé)開(kāi)啟一個(gè)Fiber,讓func邏輯跑在上面。

由于協(xié)程的暫停完全由程序控制,發(fā)生在用戶(hù)態(tài)上; 而線(xiàn)程的阻塞狀態(tài)是由操作系統(tǒng)內(nèi)核來(lái)進(jìn)行切換,發(fā)生在內(nèi)核態(tài)上。 因此協(xié)程的開(kāi)銷(xiāo)遠(yuǎn)遠(yuǎn)小于線(xiàn)程的開(kāi)銷(xiāo),也就沒(méi)有了ContextSwitch上的開(kāi)銷(xiāo)。

5、協(xié)程和線(xiàn)程的比較

pYYBAGPAt4KAC45EAACaYIy9M34037.png

六、總結(jié)

1、進(jìn)程和線(xiàn)程的區(qū)別

1)調(diào)度:線(xiàn)程作為調(diào)度和分配的基本單位,進(jìn)程作為擁有資源的基本單位;

2)并發(fā)性:不僅進(jìn)程之間可以并發(fā)執(zhí)行,同一個(gè)進(jìn)程的多個(gè)線(xiàn)程之間也可并發(fā)執(zhí)行;

3)擁有資源:進(jìn)程是擁有資源的一個(gè)獨(dú)立單位,線(xiàn)程不擁有系統(tǒng)資源,但可以訪(fǎng)問(wèn)隸屬于進(jìn)程的資源;

4)系統(tǒng)開(kāi)銷(xiāo):在創(chuàng)建或撤消進(jìn)程時(shí),由于系統(tǒng)都要為之分配和回收資源,導(dǎo)致系統(tǒng)的開(kāi)銷(xiāo)明顯大于創(chuàng)建或撤消線(xiàn)程時(shí)的開(kāi)銷(xiāo)。

2、進(jìn)程和線(xiàn)程的聯(lián)系

1)一個(gè)線(xiàn)程只能屬于一個(gè)進(jìn)程,而一個(gè)進(jìn)程可以有多個(gè)線(xiàn)程,但至少有一個(gè)線(xiàn)程;

2)資源分配給進(jìn)程,同一進(jìn)程的所有線(xiàn)程共享該進(jìn)程的所有資源;

3)處理機(jī)分給線(xiàn)程,即真正在處理機(jī)上運(yùn)行的是線(xiàn)程;

4)線(xiàn)程在執(zhí)行過(guò)程中,需要協(xié)作同步。 不同進(jìn)程的線(xiàn)程間要利用消息通信的辦法實(shí)現(xiàn)同步。

開(kāi)發(fā)者在每個(gè)線(xiàn)程中只做非常輕量的操作,比如訪(fǎng)問(wèn)一個(gè)極小的文件,下載一張極小的圖片,加載一段極小的文本等。 但是,這樣”輕量的操作“的量卻非常多。
在有大量這樣的輕量操作的場(chǎng)景下,即使可以通過(guò)使用線(xiàn)程池來(lái)避免創(chuàng)建與銷(xiāo)毀的開(kāi)銷(xiāo),但是線(xiàn)程切換的開(kāi)銷(xiāo)也會(huì)非常大,甚至于接近操作本身的開(kāi)銷(xiāo)。 對(duì)于這些場(chǎng)景,就非常需要一種可以減少這些開(kāi)銷(xiāo)的方式。 于是,協(xié)程就應(yīng)景而出,非常適合這樣的場(chǎng)景。

審核編輯:湯梓紅

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀(guān)點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • gpu
    gpu
    +關(guān)注

    關(guān)注

    28

    文章

    4673

    瀏覽量

    128592
  • 服務(wù)器
    +關(guān)注

    關(guān)注

    12

    文章

    8958

    瀏覽量

    85081
  • 線(xiàn)程
    +關(guān)注

    關(guān)注

    0

    文章

    503

    瀏覽量

    19634
  • 進(jìn)程
    +關(guān)注

    關(guān)注

    0

    文章

    201

    瀏覽量

    13938
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    解鎖高性能計(jì)算與區(qū)塊鏈應(yīng)用,阿里云Kubernetes服務(wù)召喚神龍

    網(wǎng)絡(luò)功能,包括:EIP、SLB、防、安全組、HAVIP、NAT、用戶(hù)路由等眾多高級(jí)功能。容器服務(wù)計(jì)劃結(jié)合神龍(X-Dragon)彈性裸金屬服務(wù)器將來(lái)的多網(wǎng)卡支持,提供更原生的高性能網(wǎng)
    發(fā)表于 06-13 15:52

    在DragonBoard 410c上實(shí)現(xiàn)并發(fā)處理TCP服務(wù)器

    服務(wù),讓傳感和相關(guān)的控制設(shè)備接入,為此,本期blog將向大家介紹如何使用gevent高性能并發(fā)處理庫(kù)在draognbaord 410c上來(lái)實(shí)現(xiàn)一個(gè)
    發(fā)表于 09-25 15:53

    華為服務(wù)器為什么可以保持高性能和高可靠性

    通過(guò)第三方調(diào)研機(jī)構(gòu)數(shù)據(jù)可以看出,華為服務(wù)器出貨量不斷攀升,得益于其持續(xù)通過(guò)高強(qiáng)度的研發(fā)投入和聚焦創(chuàng)新,從而為用戶(hù)提供可靠、高性能、簡(jiǎn)單易用的計(jì)算平臺(tái)。
    發(fā)表于 08-02 07:19

    高性能服務(wù)器開(kāi)發(fā)2018年的原創(chuàng)匯總

    高性能服務(wù)器開(kāi)發(fā) 2018 年原創(chuàng)匯總
    發(fā)表于 06-10 12:33

    高性能并發(fā)服務(wù)器架構(gòu)分享

    由于自己正在做一個(gè)高性能大用戶(hù)量的論壇程序,對(duì)高性能并發(fā)服務(wù)器架構(gòu)比較感興趣,于是在網(wǎng)上收集了不少這方面的資料和大家分享。希望能和大家交流
    發(fā)表于 09-16 06:45

    如何在Dragonbaord 410c上實(shí)現(xiàn)高性能并發(fā)處理TCP服務(wù)器

    服務(wù),讓傳感和相關(guān)的控制設(shè)備接入,為此,本期blog將向大家介紹如何使用gevent高性能并發(fā)處理庫(kù)在draognbaord 410c上來(lái)實(shí)現(xiàn)一個(gè)
    發(fā)表于 02-28 10:04 ?735次閱讀

    推薦一款免費(fèi)的高性能BT Tracker服務(wù)器

    和UDP?tracker協(xié)議。并采用高性能服務(wù)器框架和技術(shù),使得服務(wù)器可以支持并發(fā)性訪(fǎng)問(wèn),擁有較高性能
    發(fā)表于 10-31 17:14 ?1076次閱讀

    華為首款A(yù)rm架構(gòu)服務(wù)器CPU鯤鵬920,業(yè)界最高性能Arm架構(gòu)服務(wù)器CPU

    TaiShan系列服務(wù)器主要面向大數(shù)據(jù)、分布式存儲(chǔ)和ARM原生應(yīng)用等場(chǎng)景,發(fā)揮ARM架構(gòu)在多核、高能效等方面的優(yōu)勢(shì),為企業(yè)構(gòu)建高性能、低功耗的新計(jì)算平臺(tái);例如大數(shù)據(jù)場(chǎng)景,實(shí)現(xiàn)了多核并發(fā)
    的頭像 發(fā)表于 01-09 09:39 ?1.2w次閱讀

    如何理解服務(wù)器高性能

    :L1L2memorydiskinternet。 大家都知道IP是逐跳協(xié)議,也就是說(shuō)我只能從一個(gè)路由,到下一個(gè)路由,再到下一個(gè)路由,如果你的電腦到服務(wù)器,中途要經(jīng)過(guò)很多個(gè)路由
    的頭像 發(fā)表于 09-25 14:53 ?1963次閱讀

    詳解Nginx高性能的HTTP和反向代理服務(wù)器

    Nginx 是一個(gè)高性能的 HTTP 和反向代理服務(wù)器,特點(diǎn)是占用內(nèi)存少,并發(fā)能力強(qiáng),事實(shí)上 Nginx 的并發(fā)能力確實(shí)在同類(lèi)型的網(wǎng)頁(yè)服務(wù)器
    的頭像 發(fā)表于 03-16 11:23 ?2382次閱讀

    高性能整流顯著提高服務(wù)器供電效率

    電子發(fā)燒友網(wǎng)站提供《高性能整流顯著提高服務(wù)器供電效率.pdf》資料免費(fèi)下載
    發(fā)表于 07-26 09:46 ?0次下載
    <b class='flag-5'>高性能</b>整流<b class='flag-5'>器</b>顯著提高<b class='flag-5'>服務(wù)器</b>供電效率

    人工智能服務(wù)器高性能計(jì)算需求

    人工智能(AI)服務(wù)器是一種專(zhuān)門(mén)為了運(yùn)行人工智能應(yīng)用和提供大數(shù)據(jù)處理能力而設(shè)計(jì)的高性能計(jì)算機(jī)。它既可以支持本地應(yīng)用程序和網(wǎng)頁(yè),也可以為云和本地服務(wù)器提供復(fù)雜的AI模型和服務(wù)
    的頭像 發(fā)表于 12-08 09:44 ?473次閱讀

    國(guó)產(chǎn)高性能溫補(bǔ)晶振用于服務(wù)器光模塊,替換SiTime

    國(guó)產(chǎn)高性能溫補(bǔ)晶振用于服務(wù)器光模塊,替換SiTime
    的頭像 發(fā)表于 08-09 09:41 ?249次閱讀
    國(guó)產(chǎn)<b class='flag-5'>高性能</b>溫補(bǔ)晶振用于<b class='flag-5'>服務(wù)器</b>光模塊,替換SiTime

    GPU高性能服務(wù)器配置

    GPU高性能服務(wù)器作為提升計(jì)算速度和效率的關(guān)鍵設(shè)備,在各大應(yīng)用場(chǎng)景中發(fā)揮著越來(lái)越重要的作用。在此,petacloud.ai小編為你介紹GPU高性能服務(wù)器的配置要點(diǎn)。
    的頭像 發(fā)表于 10-21 10:42 ?134次閱讀

    高性能服務(wù)器有什么用處?

    、游戲運(yùn)行以及虛擬桌面部署等領(lǐng)域。通過(guò)動(dòng)態(tài)擴(kuò)展或縮減資源,高性能服務(wù)器能夠根據(jù)業(yè)務(wù)需求靈活調(diào)整計(jì)算能力,同時(shí)保證可用性和高效性能。
    的頭像 發(fā)表于 11-04 10:22 ?72次閱讀