最近看到「服務器併發處理能力」章節,被裏面的「I/O模型「搞得有點頭暈,因此這裏但願經過概念的辨析和對比,能更好的理解Linux的 I/O模型。 linux
同步(synchronous) IO和異步(asynchronous) IO,阻塞(blocking) IO和非阻塞(non-blocking)IO分別是什麼,到底有什麼區別? web
比較經常使用的IO Model有如下4種: blocking IO、nonblocking IO、IO multiplexing、asynchronous IO
因爲signal driven IO在實際中並不經常使用,因此我這隻說起剩下的四種IO Model。 編程
一、blocking IO 服務器
用戶進程進行I/O操做,一直阻塞到I/O操做完成爲止。 網絡
示例代碼 併發
while ( (n=read(STDIN_FILENO, buf, BUFSIZ) ) > 0) if (write (STDOUT_FILENO, buf, n) != n) err_sys (write error 」) ;在linux中,默認狀況下全部的socket都是blocking,當用戶進程調用了recvfrom這個系統調用,kernel就開始了IO的第一個階段:準備數據。對於network io來講,不少時候數據在一開始尚未到達(好比,尚未收到一個完整的UDP包),這個時候kernel就要等待足夠的數據到來。而在用戶進程這邊,整個進程會被阻塞。當kernel一直等到數據準備好了,它就會將數據從kernel中拷貝到用戶內存,而後kernel返回結果,用戶進程才解除block的狀態,從新運行起來。
二、non-blocking IO 異步
linux下,能夠經過設置socket使其變爲non-blocking。當對一個non-blocking socket執行讀操做時,流程是這個樣子: socket
//nbtest.c #include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <stdlib.h> #include <errno.h> char buffer[4096]; int main(int argc, char **argv) { int delay = 1, n, m = 0; if (argc > 1) delay=atoi(argv[1]); fcntl(0, F_SETFL, fcntl(0,F_GETFL) | O_NONBLOCK); /* stdin */ fcntl(1, F_SETFL, fcntl(1,F_GETFL) | O_NONBLOCK); /* stdout */ while (1) { n = read(0, buffer, 4096); if (n >= 0) m = write(1, buffer, n); if ((n < 0 || m < 0) && (errno != EAGAIN)) break; sleep(delay); } perror(n < 0 ? "stdin" : "stdout"); exit(1); }咱們用strace來跟蹤一下程序執行的結果:
out.txt的內容以下: async
能夠清楚的看到read讀取失敗的狀況。實際上,該方式須要應用程序以一種輪詢的方式來實現數據讀取,屢次無謂的系統調用會加大系統開銷,影響應整個系統的吞吐量。 函數
三、IO multiplexing
IO multiplexing這個詞可能有點陌生,可是若是我說select,epoll,大概就都能明白了。有些地方也稱這種IO方式爲event driven IO。咱們都知道,select/epoll的好處就在於單個process就能夠同時處理多個網絡鏈接的IO。它的基本原理就是select/epoll這個function會不斷的輪詢所負責的全部socket,當某個socket有數據到達了,就通知用戶進程。它的流程如圖:
當用戶進程調用了select,那麼整個進程會被block,而同時,kernel會「監視」全部select負責的socket,當任何一個socket中的數據準備好了,select就會返回。這個時候用戶進程再調用read操做,將數據從kernel拷貝到用戶進程。
這個圖和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,可是,如上圖所示,整個用戶的process實際上是一直被block的。只不過process是被select這個函數block,而不是被socket IO給block。
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <errno.h> #include <sys/poll.h> #include <fcntl.h> char buffer[4096]; int main(int argc, char **argv) { struct pollfd pfd; int n; fcntl(0, F_SETFL, fcntl(0,F_GETFL) | O_NONBLOCK); /* stdin */ pfd.fd = 0; /* stdin */ pfd.events = POLLIN; while (1) { n=read(0, buffer, 4096); if (n >= 0) write(1, buffer, n); n = poll(&pfd, 1, -1); if (n < 0) break; } perror( n<0 ? "stdin" : "stdout"); exit(1); }咱們用strace來跟蹤一下程序執行的結果:
out.txt文件:
該方式中,select(或poll)的調用仍然會阻塞進程,與通常典型的I/O不同的它是等待事件通知。可是它引入了超時機制,可讓應用程序有權力避免過長時間等待;另外一方面,若是應用程序須要讀寫多個文件,該方式能夠一顯身手。典型的應用就是telnet命令(詳細見《UNIX環境高級編程》)。
四、Asynchronous I/O
linux下的asynchronous IO其實用得不多。先看一下它的流程:
用戶進程發起read操做以後,馬上就能夠開始去作其它的事。而另外一方面,從kernel的角度,當它受到一個asynchronous read以後,首先它會馬上返回,因此不會對用戶進程產生任何block。而後,kernel會等待數據準備完成,而後將數據拷貝到用戶內存,當這一切都完成以後,kernel會給用戶進程發送一個signal,告訴它read操做完成了。
到目前爲止,已經將四個IO Model都介紹完了。如今回過頭來回答最初的那幾個問題:blocking和non-blocking的區別在哪,synchronous IO和asynchronous IO的區別在哪。
先回答最簡單的這個:blocking vs non-blocking。前面的介紹中其實已經很明確的說明了這二者的區別。調用blocking IO會一直block住對應的進程直到操做完成,而non-blocking IO在kernel還準備數據的狀況下會馬上返回。
在說明synchronous IO和asynchronous 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;
二者的區別就在於synchronous IO作」IO operation」的時候會將process阻塞。按照這個定義,以前所述的blocking IO,non-blocking IO,IO multiplexing都屬於synchronous IO。有人可能會說,non-blocking IO並無被block啊。這裏有個很是「狡猾」的地方,定義中所指的」IO operation」是指真實的IO操做,就是例子中的recvfrom這個system call。non-blocking IO在執行recvfrom這個system call的時候,若是kernel的數據沒有準備好,這時候不會block進程。可是,當kernel中數據準備好的時候,recvfrom會將數據從kernel拷貝到用戶內存中,這個時候進程是被block了,在這段時間內,進程是被block的。而asynchronous IO則不同,當進程發起IO 操做以後,就直接返回不再理睬了,直到kernel發送一個信號,告訴進程說IO完成。在這整個過程當中,進程徹底沒有被block。
各個IO Model的比較如圖所示:
通過上面的介紹,會發現non-blocking IO和asynchronous IO的區別仍是很明顯的。在non-blocking IO中,雖然進程大部分時間都不會被block,可是它仍然要求進程去主動的check,而且當數據準備完成之後,也須要進程主動的再次調用recvfrom來將數據拷貝到用戶內存。而asynchronous IO則徹底不一樣。它就像是用戶進程將整個IO操做交給了他人(kernel)完成,而後他人作完後發信號通知。在此期間,用戶進程不須要去檢查IO操做的狀態,也不須要主動的去拷貝數據。
最後,再舉幾個不是很恰當的例子來講明這四個IO Model: 有A,B,C,D四我的在釣魚: A用的是最老式的魚竿,因此呢,得一直守着,等到魚上鉤了再拉桿; B的魚竿有個功能,可以顯示是否有魚上鉤,因此呢,B就和旁邊的MM聊天,隔會再看看有沒有魚上鉤,有的話就迅速拉桿; C用的魚竿和B差很少,但他想了一個好辦法,就是同時放好幾根魚竿,而後守在旁邊,一旦有顯示說魚上鉤了,它就將對應的魚竿拉起來; D是個有錢人,乾脆僱了一我的幫他釣魚,一旦那我的把魚釣上來了,就給D發個短信。