【NIO系列】——之IO模型

wier發表於2019-03-04

這是【NIO系列】第二篇,歡迎持續關注:

【NIO系列】——之TCP探祕

上一篇我們講到了關於TCP/IP協議的一些內容,這些是網路程式設計的必備知識。在瞭解NIO之前我們必須要了解一下對應的系統層IO模型,比如java的NIO對應是那種IO模型,阻塞和同步的差異在哪裡,又是否相同。瞭解了這些更方便我們的後續的NIO探解。

一、同步、非同步、阻塞、非阻塞

同步、非同步,阻塞、非阻塞,這四種狀態常有人分不清,主要是這四種狀態的定義本身也不是很明確,所以各種解答的方式都有。常見的分類有以下:

  1. 同步阻塞IO

  2. 同步非阻塞IO

  3. 非同步非阻塞IO

針對某種IO模型,我們如何分類,可以基於POSIX對同步/非同步的定義來判別:

- A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes;

- An asynchronous I/O operation does not cause the requesting process to be blocked;

那麼從上我們可以看出:

阻塞:是否阻塞主要體現在呼叫的執行緒是否可以幹別的,關注的是程式的等待狀態

同步:是否同步體現在訊息通訊機制上

也就是說同步和非同步說的是訊息的通知機制,阻塞非阻塞說的是執行緒的狀態 。


如果說以上的定義依然無法判別,我們可以從輸入操作的兩個階段來看:

一般來說,一個輸入操作通常包括兩個不同階段:

(1)等待資料準備好;

(2)從核心向程式複製資料。

是否同步的判斷依據是:是否針對的是整個過程,也就是2個階段,是否有阻塞。

是否阻塞的判斷依據是:按程式(執行緒)等待訊息通知時的狀態角度來說的,也就是主要是針對第一階段來說。


舉例

我們舉例來說:

比如說做飯這件事,一般要分為連個步驟。

1、買菜,準備食材

2、炒菜,做出飯菜


方案一:自己動手處理。

1、去超市買菜,準備食材(阻塞,當前時段只能做一件事,且需要持續的等待)

2、回家切菜,炒菜,做出美味飯菜(阻塞,還是自己來處理)

評價: 方案一同步阻塞。首先階段一是阻塞的,所以認定為阻塞,兩個階段都是阻塞的,認定為同步的。


方案二:盒馬配送食材,自己做飯

1、網上下單,盒馬配送食材,快遞到了會敲門聯絡你。(非阻塞的,這期間你可以幹其他事)

2、拿到菜,切菜、炒菜,做出美味飯菜(阻塞)

評價:方案二為同步非阻塞。階段一為非阻塞,認定為非阻塞。階段二為阻塞,兩階段中有一個為阻塞,認定為同步。


方案三:盒馬配送,請阿姨做飯

1、網上下單,盒馬配送食材,快遞到了會敲門聯絡你。(非阻塞的,這期間你可以幹其他事)

2、網上請阿姨小時工,幫忙做這一餐,做好通知我。(非阻塞,期間可以幹其他事)

評價:方案三為非同步非阻塞。階段一為非阻塞,認定為非阻塞。階段二非阻塞,則兩階段中都沒有阻塞,認定為非同步。

那麼是否有非同步阻塞IO模型,沒有,要記得非同步狀態是包含二個階段的,如果有阻塞的過程,為何還叫非同步?

網上有很多介紹有非同步阻塞模型的,我目前查到的資料還沒有這個證明,若有找到相關論文,還請指教。目前我認為沒有這個模型的。


二、Unix 5種I/O模型

《UNIX網路程式設計:卷一》的第六章書中列出了五種IO模型:

  • 阻塞式I/O;

  • 非阻塞式I/O;

  • I/O複用(select,poll,epoll...);

  • 訊號驅動式I/O(SIGIO);

  • 非同步I/O(POSIX的aio_系列函式);

1.阻塞式I/O

同步阻塞 IO 模型是最常用的一個模型,也是最簡單的模型。在linux中,預設情況下所有的socket都是blocking。它符合人們最常見的思考邏輯。

在這個IO模型中,使用者空間的應用程式執行一個系統呼叫(recvform),這會導致應用程式阻塞,什麼也不幹,直到資料準備好,等待kernel準備好從網路上接收到的資料包 + 等待收到的報文被從kernel複製到buf中,recvfrom方法才會返回,最後程式再處理資料。

這就是阻塞式IO模型



2.非阻塞式I/O

非阻塞IO時對一個非阻塞描述符迴圈呼叫recvfrom,持續的輪詢(polling),以檢視某個操作是否就緒。與阻塞IO不一樣,"非阻塞將大的整片時間的阻塞分成N多的小的阻塞, 所以程式不斷地有機會 '被' CPU光顧"。

非阻塞的recvform系統呼叫呼叫之後,程式並沒有被阻塞,核心馬上返回給程式,如果資料還沒準備好,此時會返回一個error。程式在返回之後,可以乾點別的事情,然後再發起recvform系統呼叫。如此迴圈的進行recvform系統呼叫,檢查核心資料,直到資料準備好,再拷貝資料到程式。拷貝資料整個過程,程式仍然是屬於阻塞的狀態

這就是非阻塞式IO模型


3.I/O複用

IO multiplexing就是我們說的select,poll,epoll 。為何叫多路複用,是因為它I/O多路複用可以同時監聽多個fd,如此就減少了為每個需要監聽的fd開啟執行緒的開銷。

select呼叫是核心級別的,可以等待多個socket,能實現同時對多個IO埠進行監聽,當其中任何一個socket的資料準好了,就能返回進行可讀然後程式再進行recvform系統呼叫,將資料由核心拷貝到使用者程式,這個過程是阻塞的。

I/O複用模型會用到select、poll、epoll函式,這幾個函式也會使程式阻塞,但是和阻塞I/O所不同的的,這幾個函式可以同時阻塞多個I/O操作`。而且可以同時對多個讀操作,多個寫操作的I/O函式進行檢測,直到有資料可讀或可寫時(不是等到socket資料全部到達再處理, 而是有了一部分資料就會呼叫使用者程式來處理),才真正呼叫I/O操作函式。

IO複用有人把其成為同步非阻塞的,也有稱為同步阻塞。其實這個是否阻塞還需要看第一個階段,第一個階段有的阻塞,有的不阻塞。主要也是阻塞在select階段,屬於使用者主動等待階段,我們且規範為阻塞狀態,所以,把IO多路複用歸為同步阻塞模式

這是IO複用的模型:



select、poll、epoll的不同


4.訊號驅動式I/O

訊號驅動式I/O:首先我們允許Socket進行訊號驅動IO,並安裝一個訊號處理函式,程式繼續執行並不阻塞。當資料準備好時,程式會收到一個SIGIO訊號,可以在訊號處理函式中呼叫I/O操作函式處理資料。

也就是說第一個階段,完全是非阻塞的,等資料到達會給一個訊號通知,第二個階段recvfrom還是阻塞過程,和之上無差異。

訊號驅動式I/O 過程如下:


5.非同步I/O

非同步IO不是順序執行,使用者程式進行aio_read系統呼叫之後,無論核心資料是否準備好,都會直接返回給使用者程式,然後使用者態程式可以去做別的事情。等到socket資料準備好了,核心直接複製資料給程式,然後從核心向程式傳送通知IO兩個階段,程式都是非阻塞的



總結

針對這5中IO模型,我採用一張圖來總結一下。


三、java IO

Unix中的五種I/O模型,除訊號驅動I/O外,Java對其它四種I/O模型都有所支援。其中Java最早提供的blocking I/O即是同步阻塞I/O,而NIO即是同步非阻塞I/O,同時通過NIO實現的Reactor模式即是I/O複用模型的實現,通過AIO實現的Proactor模式即是非同步I/O模型的實現。

所以說嚴格意義上來說,通過Reactor模式實現的NIO,和unix中的I/O多路複用是相同的概念,但這是一種程式設計模型,而不是原生支援。這也是我們下面所要進行的netty講解的主要思想。


更多架構知識,歡迎關注我的公眾號,大碼候(cool_wier)




相關文章