Unix中的IO模型:幫你弄清阻塞VS非阻塞、同步VS異步

對同步和異步、阻塞和非阻塞這些名詞困惑了很久了,曾經相當然的認爲阻塞就是同步、非阻塞就是異步,這也是典型的錯誤,後來從Unix網絡編程卷1上纔看到最全面的解析,下面主要的內容來自Unix網絡編程,算是自己的一個學習筆記吧!

由於這本書中面向的是Unix編程,所以在其他的環境中IO模型可能會有稍微的不同,比如Java中的IO模型,但是也不會相差很多,畢竟Unix作爲衆多技術的鼻祖,很多的思想和實現都來自它。

首先要明白輸入和輸出操作的過程,這個過程包括兩個階段:

  • 等待數據準備好:這裏所說的準備好指的是數據已經到硬件設備上了,而不管該數據的來源是什麼,可能是通過網線或者無線電傳輸過來的010101,還有可能是本機進程通過系統調用寫到存儲器上的101011,總而言之此時硬件設備上必須要有對應的數據才能算是準備好;
  • 將數據從內核中複製到相應的進程中:更明確的說,是通過內核將硬件設備上的數據複製到進程空間中然後進行進一步的操作,這個過程其實是應用進程通過調用內核提供的系統接口完成的。

這個過程如下圖:

這裏寫圖片描述

其實在這個過程中內核空間其實是擔當了一個代理人的角色,進程通過調用內核提供的系統接口才能讀取硬件上存儲的數據。所以上面的兩個過程說的更抽象一點:一個是硬件已經存在數據,軟件能夠讀取數據。

由於上圖也涉及到了進程之間的通信,有必要說明一下,進程之間通信不一定要通過操作系統內核,比如共享內存就不會經過內核,但是其他的比如管道等還是會經過內核。

一、阻塞式I/O模型

對於寫過IO程序的人來說,這種模型一定不陌生,不管是使用C還是Java,內部默認的都是這種IO模型,這類所謂的阻塞其實是指應用進程受阻於內核提供的系統調用,該調用直到數據成功返回或者出錯才返回(其他情況下不返回),這時阻塞結束。具體如下圖:

這裏寫圖片描述

二、非阻塞式IO模型

所謂非阻塞,是和阻塞式相對應的,不過這種非阻塞也是相對的。與阻塞式中的系統調用返回時機不同,在非阻塞式中當應用進程調用系統接口時,如果數據沒有準備好,則會返回一個標誌來標識這種情況,這時系統應用知道數據沒有準備好則不會一直阻塞,而是通過隔一段時間輪詢一次,在兩次輪詢的間隙之間應用進程可以做其他的事情。具體如下圖:

這裏寫圖片描述

從上圖可以看出,所謂非阻塞式和阻塞式的區別和聯繫在於:

  • 在處理數據的第一階段不同,即確定數據是否準備好;
    • 阻塞式IO模型對這一階段不做任何干涉,如果沒準備好就不返回;
    • 非阻塞式IO模型則對內核進行輪詢,如果沒有準備好則返回一個標誌,這種方式雖然在一定程度上解放了應用進程,但是卻佔用了CPU的大量時間;
  • 在處理的數據的第二個階段,兩種模型則是完全相同的;

當然這種模型的實現需要系統內核支持讀取數據時的多狀態標識。

三、IO複用模型

IO複用模型中的“複用”是該模型的核心,究竟複用的是什麼?如何進行復用呢?這裏還是要聯繫上之前的兩個模式,在之前的兩個模式中,應用進程都是直接調用真正的IO系統接口,這個接口是面向應用進行直接讀取硬件上的數據的。但是在IO複用模型中,應用進程直接調用的是一個選擇器select/poll,這個選擇器類似於一個數字電路中的多路開關,如下圖:

這裏寫圖片描述

這種模型相當於在應用進程和直接IO系統調用之間添加了一個代理,之前的阻塞和非阻塞模型由於是直接面向IO系統調用的,可以看成爲其中有一個隱形的代理,但是隻能代理一個IO通道;但是在IO複用模型中,該代理可以代理多個IO通道,所以複用的其實是IO通道 。當有一個IO通道可以進行讀寫時,則select/poll返回告訴應用進程,此時應用進程開始執行對應的讀寫操作,這裏需要注意的是select/poll上的通道是需要應用進程自己去註冊的,通道可以是讀操作,也可以是寫操作。具體如下圖:

這裏寫圖片描述

從圖中可以看出,IO複用模型與阻塞式和非阻塞式模型的關係如下:

  • 相對於阻塞式模型,從圖中看,其實就是多了上半部分的select選擇器代理,如果當IO通道只有一個的時候,IO複用模型的效率相對於阻塞模型可能會更差一些,因爲它經過兩層系統調用,但是當IO通道多的時候,IO複用模型的效率就顯示出來了;
  • 相對於非阻塞式模型,相當於將應用進程執行的輪詢操作交給了操作系統內核的select/poll來做,但是非阻塞式模型中輪詢的是一個IO通道的狀態,而IO複用模型中輪詢的是多個IO通道的狀態;
  • IO複用模型是阻塞於select/poll系統調用,而阻塞式模型和非阻塞式模型則是阻塞於直接IO系統調用;

與IO複用模型非常相似的一種模型,是通過多線程結合阻塞式模型,這種阻塞式模型的變體看起來和IO複用模型、非阻塞式模型相同,會讓人誤以爲這種模型就是非阻塞式的,IO複用模型和非阻塞時模型的區別上面已經說明,而這種多線程結合阻塞式模型的一個非常大的不同就是它並不需要去輪詢IO通道,而是通過一個線程執行一次系統調用來執行IO系統操作,這樣就不會佔用大量CPU的時間,但是維護多線程環境則會佔用較多資源,並給編程帶來一些挑戰。

四、信號驅動式IO模型

信號驅動式IO模型與之前的非阻塞時模型和IO複用模型類似,但是對應用進程的通知不是通過輪詢實現的,而是使用信號機制來實現,這就使得在第一階段,等待數據準備的時候,應用進程確確實實的不阻塞,具體如下圖:

這裏寫圖片描述

在該圖中可以看出,其實應用進程是調用操作系統內核提供的signal信號處理接口,但是該接口不會造成阻塞而是立即返回。當數據準備好了之後內核則再返回一個信號,告訴應用程序。而之後的過程前面三種模型完全一樣,應用進程仍然會阻塞知道數據複製完畢。從第一個階段的是過程來看,極有可能的一種實現方式就是通過函數回調來完成這種通知

五、異步IO模型

其實在上面的4種模型說明後,異步IO模型就呼之欲出了,在前面4種模型中不管怎麼優化,針對的對象都是數據輸入的第一個階段,即等待數據準備好,如果將數據複製過程也考慮進來,那麼結果就清晰了,順着信號驅動模IO模型,將信號通知的時機放到數據複製完成之後,就是一步IO模型,這樣從整體上來看,應用進程從來沒有阻塞過,而是一直運行,直到被通知數據已經被複制到自己的空間中了。具體如下:

這裏寫圖片描述

六、模型對比

6.1 同步模型之間

上面所說的4種模型:

  • 阻塞式IO模型
  • 非阻塞式IO模型
  • IO複用模型
  • 信號驅動式IO模型

都是同步模型,它們的主要區別在第一階段,每個模型中應用進程阻塞的實現和方式不同,而在第二個階段則全部相同,都會阻塞於內核複製數據過程。所以不管阻塞和還是不阻塞都是同步模型。它們的區別是在準備數據的過程中,應用進程是不是阻塞。

6.2 同步模型 VS 異步模型

  • 同步模型:導致應用進程阻塞,直到IO操作完成;
  • 異步模型:不會造成應用進程阻塞;

上面5種模型的對比如下圖:

這裏寫圖片描述

要說明的是,異步和多線程並不是相同的概念,雖然我們在平時經常將兩者混用,其實它們不是一個層次上的概念,異步具體的說是要達到的目的,而多線程只是實現這個目的的一個手段,還有其他的手段,比如多進程,但是由於常用的實現異步的方式就是多線程,所以常常將兩者混淆,因此針對多線程的編程準確的來說應該是併發編程而不是異步編程。所以在上面提到的多線程結合阻塞IO模型,雖然使用了多線程,但是從本質上來說,每個線程對應的仍是阻塞IO模型,所以它也是同步模型,只不過是從主線程來看達到了異步的效果。


相關文章:

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