IO中同步非同步阻塞非阻塞的區別
阿新 • • 發佈:2019-02-14
更詳細解釋見http://yaocoder.blog.51cto.com/2668309/1308899
一、同步與非同步
同步/非同步, 它們是訊息的通知機制1. 概念解釋
A. 同步
所謂同步,就是在發出一個功能呼叫時,在沒有得到結果之前,該呼叫就不返回。
按照這個定義,其實絕大多數函式都是同步呼叫(例如sin isdigit等)。
但是一般而言,我們在說同步、非同步的時候,特指那些需要其他部件協作或者需要一定時間完成的任務。
最常見的例子就是 SendMessage。
該函式傳送一個訊息給某個視窗,在對方處理完訊息之前,這個函式不返回。
當對方處理完畢以後,該函式才把訊息處理函式所返回的值返回給呼叫者。
B. 非同步
非同步的概念和同步相對。
當一個非同步過程呼叫發出後,呼叫者不會立刻得到結果。
實際處理這個呼叫的部件是在呼叫發出後,
通過狀態、通知來通知呼叫者,或通過回撥函式處理這個呼叫。
以 Socket為例,
當一個客戶端通過呼叫 Connect函式發出一個連線請求後,呼叫者執行緒不用等待結果,可立刻繼續向下執行。
當連線真正建立起來以後,socket底層會發送一個訊息通知該物件。
C. 三種返回結果途徑
執行部件和呼叫者可以通過三種途徑返回結果:
a. 狀態、
b. 通知、
c. 回撥函式。
可以使用哪一種依賴於執行部件的實現,除非執行部件提供多種選擇,否則不受呼叫者控制。
a. 如果執行部件用狀態來通知,
那麼呼叫者就需要每隔一定時間檢查一次,效率就很低
有些初學多執行緒程式設計的人,總喜歡用一個迴圈去檢查某個變數的值,這其實是一種很嚴重的錯誤。
b. 如果是使用通知的方式,
效率則很高,因為執行部件幾乎不需要做額外的操作。
c. 至於回撥函式,
和通知沒太多區別。
2. 舉例說明
理解這兩個概念,可以用去銀行辦理業務(可以取錢,也可以存錢)來比喻:
當到銀行後,
.可以去ATM機前排隊等候 -- (排隊等候)就是同步等待訊息
.可以去大廳拿號,等到排到我的號時,
櫃檯的人會通知我輪到我去辦理業務. -- (等待別人通知)就是非同步等待訊息.
在非同步訊息通知機制中,
等待訊息者(在這個例子中就是等待辦理業務的人)往往註冊一個回撥機制,
在所等待的事件被觸發時由觸發機制(在這裡是櫃檯的人)通過某種機制(在這裡是寫在小紙條上的號碼)
找到等待該事件的人.
在select/poll 等IO 多路複用機制中就是fd,
當訊息被觸發時,觸發機制通過fd 找到處理該fd的處理函式.
3. 在實際的程式中,
同步訊息通知機制:就好比簡單的read/write 操作,它們需要等待這兩個操作成功才能返回;
同步, 是由處理訊息者自己去等待訊息是否被觸發;
非同步訊息通知機制:類似於select/poll 之類的多路複用IO 操作,
當所關注的訊息被觸發時,由訊息觸發機制通知觸發對訊息的處理.
非同步, 由觸發機制來通知處理訊息者;
還是回到上面的例子,
輪到你辦理業務, 這個就是你關注的訊息,
而辦理什麼業務, 就是對這個訊息的處理,
兩者是有區別的.
而在真實的IO 操作時: 所關注的訊息就是 該fd是否可讀寫,
而對訊息的處理是 對這個fd 進行讀寫.
同步/非同步僅僅關注的是如何通知訊息,它們對如何處理訊息並不關心,
好比說,銀行的人僅僅通知你輪到你辦理業務了,
而辦理業務什麼業務(存錢還是取錢)他們是不知道的.
二、阻塞與非阻塞
阻塞/非阻塞, 它們是程式在等待訊息(無所謂同步或者非同步)時的狀態.
1. 概念解釋
A. 阻塞
阻塞呼叫是指呼叫結果返回之前,當前執行緒會被掛起。函式只有在得到結果之後才會返回。
有人也許會把阻塞呼叫和同步呼叫等同起來,實際上他是不同的。
對於同步呼叫來說,很多時候當前執行緒還是啟用的,只是從邏輯上當前函式沒有返回而已。
socket接收資料函式recv是一個阻塞呼叫的例子。
當socket工作在阻塞模式的時候, 如果沒有資料的情況下呼叫該函式,則當前執行緒就會被掛起,直到有資料為止。
B. 非阻塞
非阻塞和阻塞的概念相對應,指在不能立刻得到結果之前,該函式不會阻塞當前執行緒,而會立刻返回。
C. 物件的阻塞模式和阻塞函式呼叫
物件是否處於阻塞模式和函式是不是阻塞呼叫有很強的相關性,但是並不是一一對應的。
阻塞物件上可以有非阻塞的呼叫方式,我們可以通過一定的API去輪詢狀態,
在適當的時候呼叫阻塞函式,就可以避免阻塞。
而對於非阻塞物件,呼叫特殊的函式也可以進入阻塞呼叫。函式select就是這樣的一個例子。
2. 舉例說明
繼續上面的那個例子,
不論是排隊等待,還是使用號碼等待通知,
如果在這個等待的過程中,
. 等待者除了等待訊息之外不能做其它的事情,那麼該機制就是阻塞的,
表現在程式中,也就是該程式一直阻塞在該函式呼叫處不能繼續往下執行.
. 相反,有的人喜歡在銀行辦理這些業務的時候一邊打打電話發發簡訊一邊等待,這樣的狀態就是非阻塞的,
因為他(等待者)沒有阻塞在這個訊息通知上,而是一邊做自己的事情一邊等待.
三、易混淆的點
很多人也會把非同步和非阻塞混淆,
因為非同步操作一般都不會在真正的IO 操作處被阻塞,
比如如果用select 函式,當select 返回可讀時再去read 一般都不會被阻塞
就好比當你的號碼排到時一般都是在你之前已經沒有人了,所以你再去櫃檯辦理業務就不會被阻塞.
可見,同步/非同步與阻塞/非阻塞是兩組不同的概念,它們可以共存組合,
而很多人之所以把同步和阻塞混淆,我想也是因為沒有區分這兩個概念,
比如阻塞的read/write 操作中,其實是把訊息通知和處理訊息結合在了一起,
在這裡所關注的訊息就是fd 是否可讀/寫,而處理訊息則是對fd 讀/寫.
當我們將這個fd 設定為非阻塞的時候,read/write 操作就不會在等待訊息通知這裡阻塞,
如果fd 不可讀/寫則操作立即返回.
四、同步/非同步與阻塞/非阻塞的組合分析
_______阻塞____________________非阻塞_____
同步 | 同步阻塞 同步非阻塞
非同步 | 非同步阻塞 非同步非阻塞
同步阻塞形式:
效率是最低的,
拿上面的例子來說,就是你專心排隊,什麼別的事都不做。
實際程式中
就是未對fd 設定O_NONBLOCK 標誌位的read/write 操作,
非同步阻塞形式:
如果在銀行等待辦理業務的人採用的是非同步的方式去等待訊息被觸發,也就是領了一張小紙條,
假如在這段時間裡他不能離開銀行做其它的事情,那麼很顯然,這個人被阻塞在了這個等待的操作上面;
非同步操作是可以被阻塞住的,只不過它不是在處理訊息時阻塞,而是在等待訊息被觸發時被阻塞.
比如select 函式,
假如傳入的最後一個timeout 引數為NULL,那麼如果所關注的事件沒有一個被觸發,
程式就會一直阻塞在這個select 呼叫處.
同步非阻塞形式:
實際上是效率低下的,
想象一下你一邊打著電話一邊還需要擡頭看到底隊伍排到你了沒有,
如果把打電話和觀察排隊的位置看成是程式的兩個操作的話,
這個程式需要在這兩種不同的行為之間來回的切換,效率可想而知是低下的;
很多人會寫阻塞的read/write 操作,
但是別忘了可以對fd 設定O_NONBLOCK 標誌位,這樣就可以將同步操作變成非阻塞的了;
非同步非阻塞形式:
效率更高,
因為打電話是你(等待者)的事情,而通知你則是櫃檯(訊息觸發機制)的事情,
程式沒有在兩種不同的操作中來回切換.
比如說,這個人突然發覺自己煙癮犯了,需要出去抽根菸,
於是他告訴大堂經理說,排到我這個號碼的時候麻煩到外面通知我一下(註冊一個回撥函式),
那麼他就沒有被阻塞在這個等待的操作上面,自然這個就是非同步+非阻塞的方式了.
如果使用非同步非阻塞的情況,
比如aio_*組的操作,當發起一個aio_read 操作時,函式會馬上返回不會被阻塞,
當所關注的事件被觸發時會呼叫之前註冊的回撥函式進行處理,