IO中同步、異步與阻塞、非阻塞的區別

一、同步與異步
同步/異步, 它們是消息的通知機制

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 操作時,函數會馬上返回不會被阻塞,
  當所關注的事件被觸發時會調用之前註冊的回調函數進行處理.



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