一口氣說出 5 種 IO 模型,蒙圈了!

點擊下方“IT牧場”,選擇“設爲星標”

一、基本概念

五種IO模型包括:阻塞IO、非阻塞IO、IO多路複用、信號驅動IO、異步IO

首先需要了解下系統調用的幾個函數和基本概念。

1.1 簡單介紹幾個系統調用函數

由於我對於C語言不熟悉,幾個系統函數參考了一些文章,如果錯誤歡迎指出!

recvfrom Linux系統提供給用戶用於接收網絡IO的系統接口。從套接字上接收一個消息,可同時應用於面向連接和無連接的套接字。

如果此係統調用返回值<0,並且 errno爲EWOULDBLOCK或EAGAIN(套接字已標記爲非阻塞,而接收操作被阻塞或者接收超時 )時,連接正常,阻塞接收數據(這很關鍵,前4種IO模型都設計此係統調用)。

select select系統調用允許程序同時在多個底層文件描述符上,等待輸入的到達或輸出的完成。以數組形式存儲文件描述符,64位機器默認2048個。當有數據準備好時,無法感知具體是哪個流OK了,所以需要一個一個的遍歷,函數的時間複雜度爲O(n)

poll 以鏈表形式存儲文件描述符,沒有長度限制。本質與select相同,函數的時間複雜度也爲O(n)

epoll 是基於事件驅動的,如果某個流準備好了,會以事件通知,知道具體是哪個流,因此不需要遍歷,函數的時間複雜度爲O(1)

sigaction 用於設置對信號的處理方式,也可檢驗對某信號的預設處理方式。Linux使用SIGIO信號來實現IO異步通知機制。

1.2 同步&異步

同步和異步是針對應用程序和內核交互而言的,也可理解爲被被調用者(操作系統)的角度來說。同步是用戶進程觸發IO操作並等待或輪詢的去查看是否就緒,而異步是指用戶進程觸發IO操作以後便開始做自己的事情,而當IO操作已經完成的時候會得到IO完成的通知,需要CPU支持

1.3 阻塞&非阻塞

阻塞和非阻塞是針對於進程在訪問數據的時候,也可理解爲調用者(程序)角度來說。根據IO操作的就緒狀態來採取的不同的方式。阻塞方式下讀取或寫入方法將一直等待,而非阻塞方式下讀取或寫入方法會立即返回一個狀態值。

下午擼代碼餓了,好久沒喫KFC了,決定去整個全家桶 ,這一切都要從一個全家桶說起~

我跑去肯德基買全家桶,但是很不巧,輪到我時,全家桶賣完了,我只能等着新做一份 ...

二、阻塞IO模型

學習過操作系統的知識後,可以知道:不管是網絡IO還是磁盤IO,對於讀操作而言,都是等到網絡的某個數據分組到達後/數據準備好後,將數據拷貝到內核空間的緩衝區中,再從內核空間拷貝到用戶空間的緩衝區

此時我已飢渴難耐,全程盯着後廚,等待着一分一秒(別多想 ),終於全家桶做好了,在此期間雖然什麼事也沒幹,但是最後能喫到全家桶,我很幸福。此處需要一個清新的腦回路,我就是程序,我想要全家桶,於是發起了系統調用,而後廚加工的過程就是在做數據準備和拷貝工作。全家桶最終到手,數據終於從內核空間拷貝到了用戶空間。

簡單看下執行流程

阻塞IO模型

接下來發揮看圖說話的專長了:阻塞IO的執行過程是進程進行系統調用等待內核將數據準備好並複製到用戶態緩衝區後,進程放棄使用CPU一直阻塞在此,直到數據準備好。

三、非阻塞IO模型

此時我每隔5分鐘詢問全家桶好了沒,在數次盤問後,終於出爐了。在每一次盤問之前,對於程序來說是非阻塞的佔用CPU資源,可以做其他事情。

每次應用程序詢問內核是否有數據準備好。如果就緒,就進行拷貝操作;如果未就緒,就不阻塞程序,內核直接返回未就緒的返回值,等待用戶程序下一個輪詢。

非阻塞IO模型

大致經歷兩個階段:

  • 等待數據階段:未阻塞, 用戶進程需要盲等,不停的去輪詢內核。
  • 數據複製階段:阻塞,此時進行數據複製。

在這兩個階段中,用戶進程只有在數據複製階段被阻塞了,而等待數據階段沒有阻塞,但是用戶進程需要盲等,不停地輪詢內核,看數據是否準備好。

四、IO多路複用模型

排了很長的隊,終於輪到我支付後,拿到了一張小票,上面有號次。當全家桶出爐後,會喊相應的號次來取。KFC營業員小姐姐打小票出號次的動作相當於操作系統多開了個線程,專門接收客戶端的連接。我只關注叫到的是不是我的號,因此程序還需在服務端註冊我想監聽的事件類型。

多路複用一般都是用於網絡IO,服務端與多個客戶端的建立連接。下面是神奇的多路複用執行過程:

IO多路複用模型

相比於阻塞IO模型,多路複用只是多了一個select/poll/epoll函數。select函數會不斷地輪詢自己所負責的文件描述符/套接字的到達狀態,當某個套接字就緒時,就對這個套接字進行處理。select負責輪詢等待,recvfrom負責拷貝。當用戶進程調用該select,select會監聽所有註冊好的IO,如果所有IO都沒註冊好,調用進程就阻塞。

對於客戶端來說,一般感受不到阻塞,因爲請求來了,可以用放到線程池裏執行;但對於執行select的操作系統而言,是阻塞的,需要阻塞地等待某個套接字變爲可讀

IO多路複用其實是阻塞在select,poll,epoll這類系統調用上的,複用的是執行select,poll,epoll的線程。

五、信號驅動IO模型

跑KFC嫌麻煩,剛好有個會員,直接點份外賣,美滋滋。當外賣送達時,會收到取餐電話(信號)。在收到取餐電話之前,我可以愉快地喫雞或者學習。

當數據報準備好的時候,內核會嚮應用程序發送一個信號,進程對信號進行捕捉,並且調用信號處理函數來獲取數據報。

信號驅動IO模型

該模型也分爲兩個階段:

  • 數據準備階段:未阻塞,當數據準備完成之後,會主動的通知用戶進程數據已經準備完成,對用戶進程做一個回調。
  • 數據拷貝階段:阻塞用戶進程,等待數據拷貝。

六、異步IO模型

此時科技的發展已經超乎想象了,外賣機器人將全家桶自動送達並轉換成營養快速注入我的體內,同時還能得到口感的滿足。注入結束後,機器人會提醒我注入完畢。在這個期間我可以放心大膽的玩,甚至注射的時候也不需要停下來

類比一下,就是用戶進程發起系統調用後,立刻就可以開始去做其他的事情,然後直到I/O數據準備好並複製完成後,內核會給用戶進程發送通知,告訴用戶進程操作已經完成了。

異步IO模型

特點:

  1. 異步I/O執行的兩個階段都不會阻塞讀寫操作,由內核完成。
  2. 完成後內核將數據放到指定的緩衝區, 通知應用程序來取。

七、Java中的BIO,NIO,AIO

操作系統的IO模型是底層基石,Java對於IO的操作其實就是進一步的封裝。適配一些系統調用方法,讓我們玩地更爽。BIO,NIO,AIO涉及相關實操代碼已收錄至我的github,歡迎star~

7.1 BIO--同步阻塞的編程方式

JDK1.4之前常用的編程方式。 實現過程:首先在服務端啓動一個ServerSocket來監聽網絡請求,客戶端啓動Socket發起網絡請求,默認情況下ServerSocket會建立一個線程來處理此請求,如果服務端沒有線程可用,客戶端則會阻塞等待或遭到拒絕併發效率比較低

服務器實現的模式是一個連接一個線程,若有客戶端有連接請求服務端就需要啓動一個線程進行處理,如果這個連接不做任何事情會造成不必要的線程開銷。當然,也可以通過線程池機制改善。

使用場景 BIO適用於連接數目比較小且固定的架構,對服務器資源要求高,併發侷限於應用中。

7.2 NIO--同步非阻塞的編程方式

7.2.1 NIO簡介

NIO 本身是基於事件驅動思想來完成的,當 socket 有流可讀或可寫入時,操作系統會相應地通知應用程序進行處理,應用再將流讀取到緩衝區或寫入操作系統。一個有效的請求對應一個線程,當連接沒有數據時,是沒有工作線程來處理的。

服務器實現模式爲一個請求一個通道,即客戶端發送的連接請求都會註冊到多路複用器上,多路複用器輪詢到連接有 I/O 請求時啓動一個線程進行處

使用場景 NIO 方式適用於連接數目多且連接比較短(輕操作)的架構,比如聊天服務器,併發侷限於應用中,編程複雜,JDK1.4 開始支持。

7.2.2 NIO中的幾種重要角色

有緩衝區Buffer,通道Channel,多路複用器Selector。

7.2.2.1 Buffer

在NIO庫中,所有數據都是用緩衝區(用戶空間緩衝區)處理的。在讀取數據時,它是直接讀到緩衝區中的;在寫入數據時,也是寫入到緩衝區中。任何時候訪問NIO中的數據,都是通過緩衝區進行操作。緩衝區實際上是一個數組,並提供了對數據的結構化訪問以及維護讀寫位置等信息。

Buffer的應用固定邏輯

相關的代碼我會更新至github~

寫操作順序

  1. clear()
  2. put() -> 寫操作
  3. flip() ->重置遊標
  4. SocketChannel.write(buffer); ->將緩存數據發送到網絡的另一端
  5. clear()

讀操作順序

  1. clear()
  2. SocketChannel.read(buffer); ->從網絡中讀取數據
  3. buffer.flip()
  4. buffer.get() ->讀取數據
  5. buffer.clear()

7.2.2.2 Channel

nio中對數據的讀取和寫入要通過Channel,它就像水管一樣,是一個通道。通道不同於流的地方就是通道是雙向的,可以用於讀、寫和同時讀寫操作。

7.2.2.3 Selector

多路複用器,用於註冊通道。客戶端發送的連接請求都會註冊到多路複用器上,多路複用器輪詢到連接有I/O請求時才啓動一個線程進行處理

7.3 AIO--異步非阻塞編程方式

進行讀寫操作時,只須直接調用api的read或write方法即可。一個有效請求對應一個線程,客戶端的IO請求都是OS先完成了再通知服務器應用去啓動線程進行處理。

使用場景 AIO 方式使用於連接數目多且連接比較長(重操作)的架構,比如相冊服務器,充分調用 OS 參與併發操作,編程比較複雜,JDK1.7 開始支持。

總結

從效率上來說,可以簡單理解爲阻塞IO<非阻塞IO<多路複用IO<信號驅動IO<異步IO。從同步和異步來說,只有異步IO模型是異步的,其他均爲同步。

乾貨分享

最近將個人學習筆記整理成冊,使用PDF分享。關注我,回覆如下代碼,即可獲得百度盤地址,無套路領取!

001:《Java併發與高併發解決方案》學習筆記;002:《深入JVM內核——原理、診斷與優化》學習筆記;003:《Java面試寶典》004:《Docker開源書》005:《Kubernetes開源書》006:《DDD速成(領域驅動設計速成)》007:全部008:加技術羣討論

關注我

喜歡就點個"在看"唄^_^

本文分享自微信公衆號 - IT牧場(itmuch_com)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

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