天天看點

IO系列之(一)同步、異步、阻塞、非阻塞

原文:https://www.cnblogs.com/songjy2116/p/7711368.html

Linux支援同步IO,也支援異步IO,是以分為同步阻塞BIO、同步非阻塞NIO,異步阻塞NIO,異步非阻塞。

一、同步阻塞BIO

這是早期Linux常用的IO方式,在這個模型中,使用者空間的應用程式執行一個系統調用,這會導緻應用程式阻塞。這意味着應用程式會一直阻塞,直到系統調用完成為止(資料傳輸完成或發生錯誤)。調用應用程式處于一種不再消費 CPU 而隻是簡單等待響應的狀态,是以從處理的角度來看,這是非常有效的。圖 1 給出了傳統的阻塞 I/O 模型,這也是目前應用程式中最為常用的一種模型。其行為非常容易了解,其用法對于典型的應用程式來說都非常有效。在調用 

read

 系統調用時,應用程式會阻塞并對核心進行上下文切換。然後會觸發讀操作,當響應傳回時(從我們正在從中讀取的裝置中傳回),資料就被移動到使用者空間的緩沖區中。然後應用程式就會解除阻塞(

read

 調用傳回)。

IO系列之(一)同步、異步、阻塞、非阻塞

圖1 同步阻塞方式

二、同步非阻塞 I/O

同步阻塞 I/O 的一種效率稍低的變種是同步非阻塞 I/O。在這種模型中,裝置是以非阻塞的形式打開的。這意味着 I/O 操作不會立即完成,

read

操作可能會傳回一個錯誤代碼,說明這個指令不能立即滿足(

EAGAIN

 或 

EWOULDBLOCK

),如圖 2 所示。

IO系列之(一)同步、異步、阻塞、非阻塞

圖2 同步非阻塞方式

非阻塞的實作是 I/O 指令可能并不會立即滿足,需要應用程式調用許多次來等待操作完成。這可能效率不高,因為在很多情況下,當核心執行這個指令時,應用程式必須要進行忙碌等待,直到資料可用為止,或者試圖執行其他工作。正如圖 3 所示的一樣,這個方法可以引入 I/O 操作的延時,因為資料在核心中變為可用到使用者調用 

read

 傳回資料之間存在一定的間隔,這會導緻整體資料吞吐量的降低。

三、異步阻塞方式

另外一個阻塞解決方案是帶有阻塞通知的非阻塞 I/O。在這種模型中,配置的是非阻塞 I/O,然後使用阻塞 

select

 系統調用來确定一個 I/O 描述符何時有操作。使 

select

 調用非常有趣的是它可以用來為多個描述符提供通知,而不僅僅為一個描述符提供通知。對于每個提示符來說,我們可以請求這個描述符可以寫資料、有讀資料可用以及是否發生錯誤的通知。

IO系列之(一)同步、異步、阻塞、非阻塞

圖3 異步阻塞方式

四、異步非阻塞方式

最後,異步非阻塞 I/O 模型是一種處理與 I/O 重疊進行的模型。讀請求會立即傳回,說明 

read

 請求已經成功發起了。在背景完成讀操作時,應用程式然後會執行其他處理操作。當 

read

 的響應到達時,就會産生一個信号或執行一個基于線程的回調函數來完成這次 I/O 處理過程。

IO系列之(一)同步、異步、阻塞、非阻塞

圖4 異步非阻塞方式

在一個程序中為了執行多個 I/O 請求而對計算操作和 I/O 處理進行重疊處理的能力利用了處理速度與 I/O 速度之間的差異。當一個或多個 I/O 請求挂起時,CPU 可以執行其他任務;或者更為常見的是,在發起其他 I/O 的同時對已經完成的 I/O 進行操作。

五、同步與異步

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

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 操作時,函數會馬上傳回不會被阻塞,

  當所關注的事件被觸發時會調用之前注冊的回調函數進行處理.

繼續閱讀