服務器基礎:同步、異步、阻塞與非阻塞

1 同步與異步

首先來解釋同步和異步的概念,這兩個概念與消息的通知機制有關。也就是同步與異步主要是從消息通知機制角度來說的。

1.1 概念描述

同步

所謂同步就是當一個任務的完成需要依賴另一個任務時,只有等待被依賴的任務完成後,纔可進行,這是一種可靠的任務序列.要麼都成功或都失敗,兩個任務的狀態可以保持一致.

異步

所謂異步是不需要等待依賴的任務完成,只是通知依賴的任務要完成什麼工作並且執行,只要自己完成了整個任務就算完成了,至於依賴的任務是否完成,本身無法確定,所以他是不可靠任務序列.

1.2 消息通知

當一個同步調用發出後,調用者要一直等待返回消息(結果)通知後,才能進行後續的執行;
當一個異步調用發出後,調用者不能立刻得到返回消息(結果).實際處理這個調用的部件在完成後,通過狀態、通知和回調來通知調用者。
這裏提到的部件和調用者通過三種途徑返回結果:狀態、通知和回調.使用哪一種通知機制,依賴於執行部件的實現,除非執行部件提供選擇,否則不受調用者控制.

//如果執行部件用狀態來通知,那麼調用者就需要每隔一定時間檢查一次,效率就很低

//如果是使用通知的方式,效率則很高,因爲執行部件不需要做額外的操作。至於回調函數,其實和通知沒太多區別。

1.3 場景比喻

舉個例子,比如我去銀行辦理業務,可能會有兩種方式:

//1. 排隊等候
//2. 取一個小紙條上面有我的號碼,等到排到我這個號時由櫃檯的人通知我去辦理業務;

第一種就是同步等待消息通知,也就是我需要一直等待銀行辦理業務.
第二種就是異步等待消息通知,在異步消息處理中,等待消息通知者(辦業務的人)往往註冊一個回調機制,在所等待的事件被觸發時由觸發機制(辦理業務者)通過某種規則(條上的號碼)找到等待該事件的人

2 阻塞與非阻塞

阻塞和非阻塞主要是從程序(線程)等待消息通知時的狀態角度來說的.

2.1 概念描述

阻塞調用是指調用結果返回之前,當前線程會被掛起,一直處於等待通知狀態,不能執行其他業務.函數只有在得到結果之前才返回.

阻塞調用是指調用結果返回之前,當前線程會被掛起,一直處於等待消息通知,不能夠執行其他業務。函數只有在得到結果之後纔會返回。

有人也許會把阻塞調用和同步調用等同起來,實際上它們是不同的。

1.對於同步調用來說,很多時候當前線程可能是激活的,只是從邏輯上來說,當前漢書沒有返回而已,此時,這個線程可能也會處理其他的消息.

對於阻塞調用來說,當前線程會被掛起等待當前函數返回;

還有一點,在這裏先擴展下:

//1.如果這個線程在等待當前函數返回時,仍在執行其他消息處理,那這種情況叫做同步非阻塞

//2.如果這個線程在等待當前函數返回時,沒有執行其他消息處理,而是出去掛起等待狀態,這種情況叫做同步阻塞.

同步的實現方式會有兩種: 同步阻塞、同步非阻塞;
異步的實現方式也有兩種: 異步阻塞、異步非阻塞;

2.非阻塞,指在不能立刻得到結果之前,該函數不會阻塞當前線程,而會立刻返回。雖然表面上看非阻塞的方式可以明顯的提高CPU的利用率,但是也帶了另外一種後果就是系統的線程切換增加。增加的CPU執行時間能不能補償系統的切換成本需要好好評估。

2.2 場景比喻

繼續上面的那個例子,不論是排隊還是使用號碼等待通知,如果在這個等待的過程中,等待者除了等待消息通知之外不能做其它的事情,那麼該機制就是阻塞的表現在程序中,也就是該程序一直阻塞在該函數調用處不能繼續往下執行。

相反,有的人喜歡在銀行辦理這些業務的時候一邊打打電話發發短信一邊等待,這樣的狀態就是非阻塞的,因爲他(等待者)沒有阻塞在這個消息通知上,而是一邊做自己的事情一邊等待。

但是需要注意了,同步非阻塞形式實際上是效率低下的,想象一下你一邊打着電話一邊還需要擡頭看到底隊伍排到你了沒有。如果把打電話和觀察排隊的位置看成是程序的兩個操作的話,這個程序需要在這兩種不同的行爲之間來回的切換,效率可想而知是低下的;而異步非阻塞形式卻沒有這樣的問題,因爲打電話是你(等待者)的事情,而通知你則是櫃檯(消息觸發機制)的事情,程序沒有在兩種不同的操作中來回切換。

3 同步/異步與阻塞/非阻塞

同步阻塞

效率是最低的,
拿上面的例子來說,就是你專心排隊,什麼別的事都不做.
實際程序中:就是未對 fd 設置O_NONBLOCK標誌位的read/write操作;

異步阻塞

如果在銀行等待辦理業務的人採用的是異步的方式去等待消息被觸發(通知),也就是領了一張小紙條,假如在這段時間裏他不能離開銀行做其它的事情,那麼很顯然,這個人被阻塞在了這個等待的操作上面;

異步操作是可以被阻塞住的,只不過它不是在處理消息時阻塞,而是在等待消息通知時被阻塞

比如select 函數,假如傳入的最後一個timeout參數爲NULL,那麼如果所關注的事件沒有一個被觸發,程序就會一直阻塞在這個select 調用處。

同步非阻塞

實際上是效率低下的,

想象一下你一邊打着電話一邊還需要擡頭看到底隊伍排到你了沒有,如果把打電話和觀察排隊的位置看成是程序的兩個操作的話,這個程序需要在這兩種不同的行爲之間來回的切換,效率可想而知是低下的。

很多人會寫阻塞的read/write 操作,但是別忘了可以對fd設置O_NONBLOCK 標誌位,這樣就可以將同步操作變成非阻塞的了。

異步非阻塞

效率更高,

因爲打電話是你(等待者)的事情,而通知你則是櫃檯(消息觸發機制)的事情,程序沒有在兩種不同的操作中來回切換。

比如說,這個人突然發覺自己煙癮犯了,需要出去抽根菸,於是他告訴大堂經理說,排到我這個號碼的時候麻煩到外面通知我一下(註冊一個回調函數),那麼他就沒有被阻塞在這個等待的操作上面,自然這個就是異步+非阻塞的方式了。

如果使用異步非阻塞的情況,比如aio_*組的操作,當發起一個aio_read操作時,函數會馬上返回不會被阻塞,當所關注的事件被觸發時會調用之前註冊的回調函數進行處理。

注意(阻塞節點)

很多人會把同步和阻塞混淆,我想是因爲很多時候同步操作會以阻塞的形式表現出來,比如很多人會寫阻塞的read/write操作,但是別忘了可以對fd設置O_NONBLOCK標誌位,這樣就可以將同步操作變成非阻塞的了。但最根本是因爲沒有區分這兩個概念,比如阻塞的read/write操作中,其實是把消息通知機制和等待消息通知的狀態結合在了一起,在這裏所關注的消息就是fd是否可讀/寫,而等待消息通知的狀態則是對fd可讀/寫等待過程中程序(線程)的狀態。當我們將這個fd設置爲非阻塞的時候,read/write操作就不會在等待消息通知這裏阻塞,如果fd不可讀/寫則操作立即返回。

同樣的,很多人也會把異步和非阻塞混淆,因爲異步操作一般都不會在真正的IO操作處被阻塞,比如如果用select函數,當select返回可讀時再去read一般都不會被阻塞,而是在select函數調用處阻塞。

小明的故事

對上面所講的概念再次進行一個場景梳理,上面已經明確說明,
同步/異步關注的是消息通知的機制
而阻塞/非阻塞關注的是程序(線程)等待消息通知時的狀態。
以小明下載文件打個比方,從這兩個關注點來再次說明這兩組概念,希望能夠更好的促進大家的理解。

1.同步阻塞:小明一直盯着下載進度條,到 100% 的時候就完成。

    //同步體現在:等待下載完成通知;

    //阻塞體現在:等待下載完成通知過程中,不能做其他任務處理;

2.同步非阻塞:小明提交下載任務後就去幹別的,每過一段時間就去瞄一眼進度條,看到 100% 就完成。

    //同步體現在:等待下載完成通知;

    //非阻塞體現在:等待下載完成通知過程中,去幹別的任務了,只是時不時會瞄一眼進度條;【小明必須要在兩個任務間切換,關注下載進度】    

3.異步阻塞:小明換了個有下載完成通知功能的軟件,下載完成就“叮”一聲。不過小明仍然一動不動盯着屏幕等待“叮”的聲音(看起來很傻,不是嗎)。

//異步體現在:下載完成“叮”的一聲通知;

///阻塞體現在:等待下載完成“叮”一聲通知過程中,不能做其他任務處理;

4.異步非阻塞:仍然是那個會“叮”一聲的下載軟件,小明提交下載任務後就去幹別的,聽到“叮”的一聲就知道完成了。

//異步體現在:下載完成“叮”一聲通知;

//非阻塞體現在:等待下載完成“叮”一聲通知過程中,去幹別的任務了,只需要接收“叮”聲通知即可;【軟件處理下載任務,小明處理其他任務,不需關注進度,只需接收軟件“叮”聲通知,然後進行處理即可】

說明

也就是說,同步/異步是“下載完成消息”通知的方式(機制),而阻塞/非阻塞則是在等待“下載完成消息”通知過程中的狀態(能不能幹其他任務),在不同的場景下,同步/異步、阻塞/非阻塞的四種組合都有應用。

綜上所述,同步和異步僅僅關注的是消息如何通知的機制,而阻塞與非阻塞關注的是等待消息通知時的狀態。也就是說,同步的情況下,是由處理消息者自己去等待消息是否被觸發,而異步的情況下是由觸發機制來通知處理消息者,所以在異步機制中,處理消息者和觸發機制之間就需要一個連接的橋樑:

//在銀行的例子中,這個橋樑就是小紙條上面的號碼。

//在小明的例子中,這個橋樑就是軟件“叮”的聲音。

最後,請大家注意理解“消息通知機制”和“等待消息通知時的狀態”這兩個概念,這是理解四個概念的關鍵所在。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章