IO複用模型同步,異步,阻塞,非阻塞及實例詳解

IO模型介紹

經常使用的5種IO模型:blocking IOnonblocking IOIO multiplexingsignal driven IOasynchronous IOlinux

再說一下IO發生時涉及的對象和步驟:web

*對於一個network IO (這裏咱們以read舉例),它會涉及到兩個系統對象:*算法

  • 一個是調用這個IO的process (or thread)
  • 一個就是系統內核(kernel)

*當一個read操做發生時,它會經歷兩個階段:*編程

  • 等待數據準備,好比accept(), recv()等待數據 (Waiting for the data to be ready)
  • 將數據從內核拷貝到進程中, 好比 accept()接受到請求,recv()接收鏈接發送的數據後須要複製到內核,再從內核複製到進程用戶空間 (Copying the data from the kernel to the process)

*對於socket流而言,數據的流向經歷兩個階段:*網絡

  • 第一步一般涉及等待網絡上的數據分組到達,而後被複制到內核的某個緩衝區。
  • 第二步把數據從內核緩衝區複製到應用進程緩衝區。

記住這兩點很重要,由於這些IO Model的區別就是在兩個階段上各有不一樣的狀況。多線程

阻塞 I/O(blocking IO)

在linux中,默認狀況下全部的socket都是blocking,一個典型的讀操做流程大概是這樣:併發

阻塞IO流程

當用戶進程調用了recvfrom這個系統調用,kernel就開始了IO的第一個階段:準備數據(對於網絡IO來講,不少時候數據在一開始尚未到達。好比,尚未收到一個完整的UDP包。這個時候kernel就要等待足夠的數據到來)。這個過程須要等待,也就是說數據被拷貝到操做系統內核的緩衝區中是須要一個過程的。而在用戶進程這邊,整個進程會被阻塞(固然,是進程本身選擇的阻塞)。當kernel一直等到數據準備好了,它就會將數據從kernel中拷貝到用戶內存,而後kernel返回結果,用戶進程才解除block的狀態,從新運行起來。異步

因此,blocking IO的特色就是在IO執行的兩個階段都被block了。socket

非阻塞 I/O(nonblocking IO)

linux下,能夠經過設置socket使其變爲non-blocking。當對一個non-blocking socket執行讀操做時,流程是這個樣子:async

非阻塞 I/O 流程

當用戶進程發出read操做時,若是kernel中的數據尚未準備好,那麼它並不會block用戶進程,而是馬上返回一個error。從用戶進程角度講 ,它發起一個read操做後,並不須要等待,而是立刻就獲得了一個結果。用戶進程判斷結果是一個error時,它就知道數據尚未準備好,因而它能夠再次發送read操做。一旦kernel中的數據準備好了,而且又再次收到了用戶進程的system call,那麼它立刻就將數據拷貝到了用戶內存,而後返回。

因此,nonblocking IO的特色是用戶進程須要不斷的主動詢問kernel數據好了沒有。

值得注意的是,此時的非阻塞IO只是應用到等待數據上,當真正有數據到達執行recvfrom的時候,仍是同步阻塞IO來的, 從圖中的copy data from kernel to user能夠看出

I/O 多路複用( IO multiplexing)

IO multiplexing就是咱們說的select,poll,epoll,有些地方也稱這種IO方式爲event driven IO。select/epoll的好處就在於單個process就能夠同時處理多個網絡鏈接的IO。它的基本原理就是select,poll,epoll這個function會不斷的輪詢所負責的全部socket,當某個socket有數據到達了,就通知用戶進程。

I/O 多路複用流程

這個圖和blocking IO的圖其實並無太大的不一樣,事實上,還更差一些。由於這裏須要使用兩個system call (select 和 recvfrom),而blocking IO只調用了一個system call (recvfrom)。可是,用select的優點在於它能夠同時處理多個connection。

因此,若是處理的鏈接數不是很高的話,使用select/epoll的web server不必定比使用multi-threading + blocking IO的web server性能更好,可能延遲還更大。select/epoll的優點並非對於單個鏈接能處理得更快,而是在於能處理更多的鏈接。)

在IO multiplexing Model中,實際中,對於每個socket,通常都設置成爲non-blocking,由於只有設置成non-blocking 才能使單個線程/進程不被阻塞(或者說鎖住),能夠繼續處理其餘socket。如上圖所示,整個用戶的process實際上是一直被block的。只不過process是被select這個函數block,而不是被socket IO給block。

當用戶進程調用了select,那麼整個進程會被block,而同時,全部進來的鏈接socket,都會加入到select 監視列表裏面, 由kernel會「監視」全部select負責的socket,,而以後 select(poll, epoll 等)函數會不斷的輪詢所負責的全部socket,這些socket都是非阻塞的存在於select 監視列表,select 使用某種監視機制檢查某個socket是否有數據到達了,當任何一個socket中的數據準備好了,select就會返回。這個時候用戶進程再調用read操做,將數據從kernel拷貝到用戶進程。

點評:
*I/O 多路複用的特色是經過一種機制一個進程能同時等待多個文件描述符,*而這些文件描述符(套接字描述符)其中的任意一個進入讀就緒狀態,select()函數就能夠返回。
*因此, IO多路複用,本質上不會有併發的功能,由於任什麼時候候仍是隻有一個進程或線程進行工做,它之因此能提升效率是由於selectepoll 把進來的socket放到他們的 '監視' 列表裏面,當任何socket有可讀可寫數據立馬處理,那若是selectepoll 手裏同時檢測着不少socket, 一有動靜立刻返回給進程處理,總比一個一個socket過來,阻塞等待,處理高效率。*
固然也能夠多線程/多進程方式,一個鏈接過來開一個進程/線程處理,這樣消耗的內存和進程切換頁會耗掉更多的系統資源。
因此咱們能夠結合IO多路複用和多進程/多線程 來高性能併發,IO複用負責提升接受socket的通知效率,收到請求後,交給進程池/線程池來處理邏輯。

異步 I/O(asynchronous IO)

linux下的asynchronous IO其實用得不多。先看一下它的流程:

異步IO 流程

用戶進程發起read操做以後,馬上就能夠開始去作其它的事。而另外一方面,從kernel的角度,當它受到一個asynchronous read以後,首先它會馬上返回,因此不會對用戶進程產生任何block。而後,kernel會等待數據準備完成,而後將數據拷貝到用戶內存,當這一切都完成以後,kernel會給用戶進程發送一個signal,告訴它read操做完成了。

阻塞IO,非阻塞IO 與 同步IO, 異步IO的區別和聯繫

阻塞IO VS 非阻塞IO:

概念:阻塞和非阻塞關注的是程序在等待調用結果(消息,返回值)時的狀態.阻塞調用是指調用結果返回以前,當前線程會被掛起。調用線程只有在獲得結果以後纔會返回。非阻塞調用指在不能馬上獲得結果以前,該調用不會阻塞當前線程。

例子:你打電話問書店老闆有沒有《分佈式系統》這本書,你若是是阻塞式調用,你會一直把本身「掛起」,直到獲得這本書有沒有的結果,若是是非阻塞式調用,你無論老闆有沒有告訴你,你本身先一邊去玩了, 固然你也要偶爾過幾分鐘check一下老闆有沒有返回結果。在這裏阻塞與非阻塞與是否同步異步無關。跟老闆經過什麼方式回答你結果無關。

分析:阻塞IO會一直block住對應的進程直到操做完成,而非阻塞IO在kernel還準備數據的狀況下會馬上返回。

同步IO VS 異步IO:

概念:同步與異步同步和異步關注的是*消息通訊機制 (synchronous communication/ asynchronous communication)所謂同步,就是在發出一個調用時,在沒有獲得結果以前,該調用就不返回。可是一旦調用返回,就獲得返回值了。換句話說,就是由調用者主動等待這個調用的結果。而異步則是相反,調用在發出以後,這個調用就直接返回了,因此沒有返回結果。換句話說,當一個異步過程調用發出後,調用者不會馬上獲得結果。而是在調用發出後,被調用者*經過狀態、通知來通知調用者,或經過回調函數處理這個調用。

典型的異步編程模型好比Node.js舉個通俗的例子:你打電話問書店老闆有沒有《分佈式系統》這本書,若是是同步通訊機制,書店老闆會說,你稍等,」我查一下",而後開始查啊查,等查好了(多是5秒,也多是一天)告訴你結果(返回結果)。而異步通訊機制,書店老闆直接告訴你我查一下啊,查好了打電話給你,而後直接掛電話了(不返回結果)。而後查好了,他會主動打電話給你。在這裏老闆經過「回電」這種方式來回調。

分析:在說明同步IO和異步IO的區別以前,須要先給出二者的定義。Stevens給出的定義(實際上是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;

二者的區別就在於同步IO作」IO operation」的時候會將process阻塞。按照這個定義,以前所述的*阻塞IO,非阻塞IO ,IO複用都屬於同步IO。*有人可能會說,非阻塞IO 並無被block啊。這裏有個很是「狡猾」的地方,定義中所指的」IO operation」是指真實的IO操做,就是例子中的recvfrom這個system call。非阻塞IO在執行recvfrom這個system call的時候,若是kernel的數據沒有準備好,這時候不會block進程。可是,當kernel中數據準備好的時候,recvfrom會將數據從kernel拷貝到用戶內存中,這個時候進程是被block了,在這段時間內,進程是被block的。

而異步IO則不同,當進程發起IO 操做以後,就直接返回不再理睬了,直到kernel發送一個信號,告訴進程說IO完成。在這整個過程當中,進程徹底沒有被block。

IO模型的形象舉例

最後,再舉幾個不是很恰當的例子來講明這四個IO Model:有A,B,C,D四我的在釣魚:A用的是最老式的魚竿,因此呢,得一直守着,等到魚上鉤了再拉桿;B的魚竿有個功能,可以顯示是否有魚上鉤,因此呢,B就和旁邊的MM聊天,隔會再看看有沒有魚上鉤,有的話就迅速拉桿;C用的魚竿和B差很少,但他想了一個好辦法,就是同時放好幾根魚竿,而後守在旁邊,一旦有顯示說魚上鉤了,它就將對應的魚竿拉起來;D是個有錢人,乾脆僱了一我的幫他釣魚,一旦那我的把魚釣上來了,就給D發個短信。

Select/Poll/Epoll 輪詢機制

select,poll,epoll本質上都是同步I/O,由於他們都須要在讀寫事件就緒後本身負責進行讀寫,也就是說這個讀寫過程是阻塞的

Select/Poll/Epoll 都是IO複用的實現方式, 上面說了使用IO複用,會把socket設置成non-blocking,而後放進Select/Poll/Epoll 各自的監視列表裏面,那麼,他們的對socket是否有數據到達的監視機制分別是怎樣的?效率又如何?咱們應該使用哪一種方式實現IO複用比較好?下面列出他們各自的實現方式,效率,優缺點:

(1)select,poll實現須要本身不斷輪詢全部fd集合,直到設備就緒,期間可能要睡眠和喚醒屢次交替。而epoll其實也須要調用epollwait不斷輪詢就緒鏈表,期間也可能屢次睡眠和喚醒交替,可是它是設備就緒時,調用回調函數,把就緒fd放入就緒鏈表中,並喚醒在epollwait中進入睡眠的進程。雖然都要睡眠和交替,可是select和poll在「醒着」的時候要遍歷整個fd集合,而epoll在「醒着」的時候只要判斷一下就緒鏈表是否爲空就好了,這節省了大量的CPU時間。這就是回調機制帶來的性能提高。

(2)select,poll每次調用都要把fd集合從用戶態往內核態拷貝一次,而且要把current往設備等待隊列中掛一次,而epoll只要一次拷貝,並且把current往等待隊列上掛也只掛一次(在epoll_wait的開始,注意這裏的等待隊列並非設備等待隊列,只是一個epoll內部定義的等待隊列)。這也能節省很多的開銷。

關於我

若是文章對你有收穫,能夠收藏轉發,這會給我一個大大鼓勵喲!另外能夠關注我公衆號【碼農富哥】 (coder2025),我會持續輸出原創的算法,計算機基礎文章!

相關文章
相關標籤/搜索