經常使用4種IO模型(同步/異步/阻塞/非阻塞的概念)

常見的IO模型有四種:

服務器端編程常常須要構造高性能的IO模型html

1)同步阻塞IO(Blocking IO):即傳統的IO模型。
(2)同步非阻塞IO(Non-blocking IO):默認建立的socket都是阻塞的,非阻塞IO要求socket被設置爲NONBLOCK。
注意這裏所說的NIO並不是Java的NIO(New IO)庫。
3)IO多路複用(IO Multiplexing):即經典的Reactor設計模式,有時也稱爲異步阻塞IO,Java中的Selector和Linux中的epoll都是這種模型。 (4)異步IO(Asynchronous IO):即經典的Proactor設計模式,也稱爲異步非阻塞IO。
(5)基於信號驅動的IO(Signal Driven IO)模型,因爲該模型並不經常使用(略)

在理解關於同步和阻塞的概念前,須要知道編程

I/0 操做 主要分紅兩部分
① 數據準備,將數據加載到內核緩存(數據加載到操做系統)
② 將內核緩存中的數據加載到用戶緩存(從操做系統複製到應用中)

同步和異步的概念描述的是用戶線程與內核的交互方式設計模式

阻塞和非阻塞的概念描述的是用戶線程調用內核IO操做的方式緩存

異步就是異步

來源:關於同步、異步與阻塞、非阻塞的理解服務器

同步和異步針對應用程序來,關注的是程序中間的協做關係;網絡

阻塞與非阻塞更關注的是單個進程的執行狀態。多線程

 阻塞、非阻塞、多路IO複用,都是同步IO,異步一定是非阻塞的,因此不存在異步阻塞和異步非阻塞的說法。
真正的異步IO須要CPU的深度參與。換句話說,只有用戶線程在操做IO的時候根本不去考慮IO的執行所有都交給CPU去完成,
而本身只等待一個完成信號的時候,纔是真正的異步IO。
因此,拉一個子線程去輪詢、去死循環,或者使用select、poll、epool,都不是異步。

PS:架構

1,異步是一個相對概念,實際應用中沒有絕對的異步,現實中更多稱爲「異步」只是表明阻塞。
2,不一樣場合,語言環境,概念不同,有時候同步就表明了阻塞,異步表示非阻塞。若是細分的話,表明不一樣含義。

PS:【同步/異步】和【阻塞/非阻塞】的關注點是存在區別的:併發

【同步/異步】表示是兩個事件交互的是否有序依賴關係
同步:針對執行結果,A事件必須知道B事件的結果M後才執行獲得結果。
異步:針對執行結果,執行A事件和執行B事件沒有關係。
阻塞
/非阻塞表示執行過程出現的狀態 阻塞:針對執行者來講,執行A事件,執行過程由於條件未知足,執行狀態變成等待狀態。 非阻塞:針對執行者來講,就是事件A執行遇到未知足條件,執行另外獨立的C事件。
總結:二者之間是沒有關係的 【同步
/異步】
概念上是:事件A,B的結果之間的是否存在依賴關係;
影響上是:保證依賴數據的正確性 【阻塞
/非阻塞】
概念上是:自身執行狀態。 影響上是:阻塞致使資源浪費。

特別注意:異步只有異步,同步纔有阻塞和非阻塞的說法!

例子: 總總體看:傳統的請求,是同步的(也是阻塞的),請求響應是有序的(請求響應之間也是等待的);AJAX是異步請求(也是非阻塞的)。 同步不等於阻塞: 單個看:AJAX從客戶端執行單個請求看數據是同步,可是執行是非阻塞,在未收到響應繼續執行其餘請求。

 

1、同步阻塞IO

同步阻塞IO模型是最簡單的IO模型,用戶線程在內核進行IO操做時被阻塞。異步

 

用戶線程經過系統調用read發起IO讀操做,由用戶空間轉到內核空間。

內核等到數據包到達後,而後將接收的數據拷貝到用戶空間,完成read操做。

2、同步非阻塞IO

同步非阻塞IO是在同步阻塞IO的基礎上,將socket設置爲NIO(NONBLOCK)。

這樣作用戶線程能夠在發起IO請求後能夠當即返回。

因爲socket是非阻塞的方式,所以用戶線程發起IO請求時當即返回。

但並未讀取到任何數據,用戶線程須要不斷地發起IO請求,直到數據到達後,才真正讀取到數據,繼續執行。

3、IO多路複用

IO多路複用模型是創建在內核提供的多路分離函數select基礎之上的,

使用select函數能夠避免同步非阻塞IO模型中輪詢等待的問題。

用戶首先將須要進行IO操做的socket添加到select中,而後阻塞等待select系統調用返回。
當數據到達時,socket被激活,select函數返回。用戶線程正式發起read請求,讀取數據並繼續執行。
從流程上來看,使用select函數進行IO請求和同步阻塞模型沒有太大的區別,
甚至還多了添加監視socket,以及調用select函數的額外操做,效率更差。
可是,使用select之後最大的優點是用戶能夠在一個線程內同時處理多個socket的IO請求。
用戶能夠註冊多個socket,而後不斷地調用select讀取被激活的socket,便可達到在同一個線程內同時處理多個IO請求的目的。
而在同步阻塞模型中,必須經過多線程的方式才能達到這個目的。
然而,使用select函數的優勢並不只限於此。
雖然上述方式容許單線程內處理多個IO請求,可是每一個IO請求的過程仍是阻塞的(在select函數上阻塞),平均時間甚至比同步阻塞IO模型還要長。
若是用戶線程只註冊本身感興趣的socket或者IO請求,而後去作本身的事情,等到數據到來時再進行處理,則能夠提升CPU的利用率。

IO多路複用模型使用了Reactor設計模式實現了這一機制。

補充:IO多路複用又叫「事件驅動」

首先,要從你經常使用的IO操做談起,好比read和write,一般IO操做都是阻塞I/O的,也就是說當你調用read時,
若是沒有數據收到,那麼線程或者進程就會被掛起,直到收到數據。 這樣,當服務器須要處理1000個鏈接的的時候,並且只有不多鏈接忙碌的,
那麼會須要1000個線程或進程來處理1000個鏈接,而1000個線程大部分是被阻塞起來的。
因爲CPU的核數或超線程數通常都不大,好比4,
8,16,32,64,128,好比4個核要跑1000個線程,那麼每一個線程的時間槽很是短,而線程切換很是頻繁。
這樣是有問題的:
1,線程是有內存開銷的,1個線程可能須要512K(或2M)存放棧,那麼1000個線程就要512M(或2G)內存。
2,線程的切換,或者說上下文切換是有CPU開銷的,當大量時間花在上下文切換的時候,分配給真正的操做的CPU就要少不少。 那麼,咱們就要引入非阻塞I
/O的概念,非阻塞IO很簡單,經過fcntl(POSIX)或ioctl(Unix)設爲非阻塞模式,
這時,當你調用read時,若是有數據收到,就返回數據,若是沒有數據收到,就馬上返回一個錯誤,如EWOULDBLOCK。
這樣是不會阻塞線程了,可是你仍是要不斷的輪詢來讀取或寫入。 因而,咱們須要引入IO多路複用的概念
多路複用是指使用一個線程來檢查多個文件描述符(Socket)的就緒狀態,好比調用select和poll函數,傳入多個文件描述符,
若是有一個文件描述符就緒,則返回,不然阻塞直到超時。
獲得就緒狀態後進行真正的操做能夠在同一個線程裏執行,也能夠啓動線程執行(好比使用線程池)。 這樣在處理1000個鏈接時,只須要1個線程監控就緒狀態,對就緒的每一個鏈接開一個線程處理就能夠了,
這樣須要的線程數大大減小,減小了內存開銷和上下文切換的CPU開銷。

4、異步IO

「真正」的異步IO須要操做系統更強的支持。
在IO多路複用模型中,事件循環將文件句柄的狀態事件通知給用戶線程,由用戶線程自行讀取數據、處理數據。
而在異步IO模型中,當用戶線程收到通知時,數據已經被內核讀取完畢,並放在了用戶線程指定的緩衝區內,內核在IO完成後通知用戶線程直接使用便可。

 

異步IO模型使用了Proactor設計模式實現了這一機制。

相比於IO多路複用模型,異步IO並不十分經常使用,很多高性能併發服務程序使用IO多路複用模型+多線程任務處理的架構基本能夠知足需求。
何況目前操做系統對異步IO的支持並不是特別完善,更多的是採用IO多路複用模型模擬異步IO的方式
(IO事件觸發時不直接通知用戶線程,而是將數據讀寫完畢後放到用戶指定的緩衝區中)。
Java7以後已經支持了異步IO,感興趣的讀者能夠嘗試使用。

 

NIO技術概覽 

Socket高性能IO模型淺析

轉 網絡IO模型:同步IO和異步IO,阻塞IO和非阻塞IO 

 網絡編程:Reactor與Proactor的概念

相關文章
相關標籤/搜索