近來遇到了一些常見的概念,尤爲是網絡編程方面的概念,如:阻塞、非阻塞、異步I/O等等,對於這些概念本身也沒有太清晰的認識,只是很模糊的概念,說了解吧也瞭解,可是要讓本身準確的描述概念方面的具體細節,卻說的不那麼準確,這也是本身在這幾個方面也沒有細細考究過的緣由吧。通過看了些這幾個概念的資料,發現同步、異步、阻塞、非阻塞的概念其實也並不難以理解,在此寫下此文,歡迎拍磚,但願多多交流。編程
首先來解釋同步和異步的概念,這兩個概念與消息的通知機制有關。也就是同步與異步主要是從消息通知機制角度來講的。
網絡
所謂同步就是一個任務的完成須要依賴另一個任務時,只有等待被依賴的任務完成後,依賴的任務才能算完成,這是一種可靠的任務序列
。要麼成功都成功,失敗都失敗,兩個任務的狀態能夠保持一致。多線程
所謂異步是不須要等待被依賴的任務完成,只是通知被依賴的任務要完成什麼工做,依賴的任務也當即執行,只要本身完成了整個任務就算完成了
。至於被依賴的任務最終是否真正完成,依賴它的任務沒法肯定,因此它是不可靠的任務序列
。異步
異步的概念和同步相對
。當一個同步調用發出後,調用者要一直等待返回消息(結果)通知後
,才能進行後續的執行;當一個異步過程調用發出後,調用者不能馬上獲得返回消息(結果)。實際處理這個調用的部件在完成後,經過狀態、通知和回調來通知調用者
。函數
這裏提到執行部件和調用者經過三種途徑返回結果:狀態、通知和回調
。使用哪種通知機制,依賴於執行部件的實現
,除非執行部件提供多種選擇,不然不受調用者控制
。spa
若是執行部件用狀態來通知,那麼調用者就須要每隔必定時間檢查一次,效率就很低(有些初學多線程編程的人,總喜歡用一個循環去檢查某個變量的值,這實際上是一種很嚴重的錯誤);線程
若是是使用通知的方式,效率則很高,由於執行部件幾乎不須要作額外的操做。至於回調函數,其實和通知沒太多區別。code
舉個例子,好比我去銀行辦理業務,可能會有兩種方式:事件
選擇排隊等候;回調函數
另種選擇取一個小紙條上面有個人號碼,等到排到我這一號時由櫃檯的人通知我輪到我去辦理業務了;
第一種:前者(排隊等候)就是同步等待消息通知
,也就是我要一直在等待銀行辦理業務狀況;
第二種:後者(等待別人通知)就是異步等待消息通知
。在異步消息處理中,等待消息通知者(在這個例子中就是等待辦理業務的人)每每註冊一個回調機制
,在所等待的事件被觸發時由觸發機制(在這裏是櫃檯的人)經過某種機制(在這裏是寫在小紙條上的號碼,喊號)找到等待該事件的人。
阻塞和非阻塞這兩個概念與程序(線程)等待消息通知(無所謂同步或者異步)時的狀態有關。也就是說阻塞與非阻塞主要是程序(線程)等待消息通知時的狀態角度來講的。
阻塞調用是指調用結果返回以前,當前線程會被掛起,一直處於等待消息通知,不可以執行其餘業務
。函數只有在獲得結果以後纔會返回。
有人也許會把阻塞調用和同步調用等同起來,實際上它們是不一樣的。
對於同步調用來講,不少時候當前線程可能仍是激活的,只是從邏輯上當前函數沒有返回而已,此時,這個線程可能也會處理其餘的消息
。還有一點,在這裏先擴展下:
(a) 若是這個線程在等待當前函數返回時,仍在執行其餘消息處理,那這種狀況就叫作同步非阻塞;
(b) 若是這個線程在等待當前函數返回時,沒有執行其餘消息處理,而是處於掛起等待狀態,那這種狀況就叫作同步阻塞;
因此同步的實現方式會有兩種:同步阻塞、同步非阻塞;同理,異步也會有兩種實現:異步阻塞、異步非阻塞;
對於阻塞調用來講,則當前線程就會被掛起等待當前函數返回;
非阻塞和阻塞的概念相對應,指在不能馬上獲得結果以前,該函數不會阻塞當前線程,而會馬上返回
。雖然表面上看非阻塞的方式能夠明顯的提升CPU的利用率,可是也帶了另一種後果就是系統的線程切換增長
。增長的CPU執行時間能不能補償系統的切換成本須要好好評估
。
繼續上面的那個例子,不管是排隊仍是使用號碼等待通知,若是在這個等待的過程當中,等待者除了等待消息通知以外不能作其它的事情,那麼該機制就是阻塞的
,表如今程序中,也就是該程序一直阻塞在該函數調用處不能繼續往下執行。
相反,有的人喜歡在銀行辦理這些業務的時候一邊打打電話發發短信一邊等待,這樣的狀態就是非阻塞的
,由於他(等待者)沒有阻塞在這個消息通知上,而是一邊作本身的事情一邊等待。
可是須要注意了,同步非阻塞形式其實是效率低下的
,想象一下你一邊打着電話一邊還須要擡頭看到底隊伍排到你了沒有。若是把打電話和觀察排隊的位置當作是程序的兩個操做的話,這個程序須要在這兩種不一樣的行爲之間來回的切換,效率可想而知是低下的;而異步非阻塞形式卻沒有這樣的問題
,由於打電話是你(等待者)的事情,而通知你則是櫃檯(消息觸發機制)的事情,程序沒有在兩種不一樣的操做中來回切換。
同步阻塞形式
效率是最低的,
拿上面的例子來講,就是你專心排隊,什麼別的事都不作。
實際程序中:就是未對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函數調用處阻塞
。
對上面所講的概念再次進行一個場景梳理,上面已經明確說明,同步/異步關注的是消息通知的機制,而阻塞/非阻塞關注的是程序(線程)等待消息通知時的狀態
。以小明下載文件打個比方,從這兩個關注點來再次說明這兩組概念,但願可以更好的促進你們的理解。
同步阻塞:小明一直盯着下載進度條,到 100% 的時候就完成。
同步體如今:等待下載完成通知;
阻塞體如今:等待下載完成通知過程當中,不能作其餘任務處理;
同步非阻塞:小明提交下載任務後就去幹別的,每過一段時間就去瞄一眼進度條,看到 100% 就完成。
同步體如今:等待下載完成通知;
非阻塞體如今:等待下載完成通知過程當中,去幹別的任務了,只是時不時會瞄一眼進度條;【小明必需要在兩個任務間切換,關注下載進度】
異步阻塞:小明換了個有下載完成通知功能的軟件,下載完成就「叮」一聲。不太小明仍然一直等待「叮」的聲音(看起來很傻,不是嗎)。
異步體如今:下載完成「叮」一聲通知;
阻塞體如今:等待下載完成「叮」一聲通知過程當中,不能作其餘任務處理;
異步非阻塞:仍然是那個會「叮」一聲的下載軟件,小明提交下載任務後就去幹別的,聽到「叮」的一聲就知道完成了。
異步體如今:下載完成「叮」一聲通知;
非阻塞體如今:等待下載完成「叮」一聲通知過程當中,去幹別的任務了,只須要接收「叮」聲通知便可;【軟件處理下載任務,小明處理其餘任務,不需關注進度,只需接收軟件「叮」聲通知,便可】
也就是說,同步/異步是「下載完成消息」通知的方式(機制),而阻塞/非阻塞則是在等待「下載完成消息」通知過程當中的狀態(能不能幹其餘任務)
,在不一樣的場景下,同步/異步、阻塞/非阻塞的四種組合都有應用。
因此,綜上所述,同步和異步僅僅是關注的消息如何通知的機制,而阻塞與非阻塞關注的是等待消息通知時的狀態
。也就是說,同步的狀況下,是由處理消息者本身去等待消息是否被觸發,而異步的狀況下是由觸發機制來通知處理消息者
,因此在異步機制中,處理消息者和觸發機制之間就須要一個鏈接的橋樑
:
在銀行的例子中,這個橋樑就是小紙條上面的號碼。
在小明的例子中,這個橋樑就是軟件「叮」的聲音。