1. 程式人生 > >【Socket程式設計】篇五之IO同步、非同步、阻塞、非阻塞

【Socket程式設計】篇五之IO同步、非同步、阻塞、非阻塞

參考Richard Stevens的“UNIX® Network Programming Volume 1, Third Edition: The Sockets Networking ”,6.2節“I/O Models ”

我們詳細探討一下 I/O 模型:

1)同步模型(synchronous I/O)

_1、阻塞I/O(bloking I/O)

_2、非阻塞I/O(non-blocking I/O)

_3、多路複用I/O(multiplexing I/O)

_4、訊號驅動式I/O(signal-driven I/O)

2)非同步模型(asynchronous I/O)

對於一個network IO (以read舉例),它會涉及到兩個系統物件,一個是呼叫這個IO的process (thread),另一個就是系統核心 (kernel)。當一個read操作發生時,它會經歷兩個階段:

1 、等待資料準備 (Waiting for the data to be ready);

2 、將資料從核心拷貝到程序中 (Copying the data from the kernel to the process)。

記住這兩點很重要,因為這些IO Model的區別就是在兩個階段上各有不同的情況。

1、阻塞IO                                                                                                                                                                        

linux中,預設情況下所有的socket都是blocking IO,一個典型的讀操作流程大概是這樣:


當用戶程序呼叫了 recvfrom 函式後,kernel 就開始了IO的第一個階段:準備資料。對於network IO來說,很多時候資料在一開始還沒有到達(比如,還沒有收到一個完整的UDP包),這個時候kernel就要等待足夠的資料到來。而在使用者程序這邊,整個程序會被阻塞。當kernel一直等到資料準備好了,它就會將資料從kernel中拷貝到使用者記憶體,然後kernel返回結果,使用者程序才解除阻塞的狀態,重新執行起來。所以,blocking IO的特點就是在IO執行的兩個階段都被block了。

2、非阻塞IO                                                                                                                                                                     


當用戶程序發出read操作時,如果kernel中的資料還沒有準備好,那麼它並不會block使用者程序,而是立刻返回一個error。從使用者程序角度講 ,它發起一個read操作後,並不需要等待,而是馬上就得到了一個結果。使用者程序判斷結果是一個error時,它就知道資料還沒有準備好,於是它可以再次傳送read操作。一旦kernel中的資料準備好了,並且又再次收到了使用者程序的system call,那麼它馬上就將資料拷貝到了使用者記憶體,然後返回。所以,使用者程序其實是需要不斷的主動詢問kernel資料好了沒有。

3、IO多路複用                                                                                                                                                                 

IO多路複用常用的方法有:select、poll以及epoll三種。IO多路複用的好處就在於單個process就可以同時處理多個網路連線的IO。

以select為例:


當用戶程序呼叫了select,那麼整個程序會被阻塞,而同時kernel會“監視”所有select負責的socket,當任何一個socket中的資料準備好了,select就會返回。這個時候使用者程序再呼叫read操作,將資料從kernel拷貝到使用者程序。

這個圖和blocking IO的圖其實並沒有太大的不同,事實上,還更差一些。因為這裡需要使用兩個system call (select 和 recvfrom),而blocking IO只調用了一個system call (recvfrom)。但是,用select的優勢在於它可以同時處理多個socket。(所以,如果處理的連線數不是很多的話,使用 select/poll/epoll 的web server不一定比使用multi-threading + blocking IO的web server效能更好,可能延遲還更大。select/poll/epoll的優勢並不是對於單個連線能處理得更快,而是在於能處理更多的連線。)
在IO multiplexing Model中,實際中,對於每一個socket,一般都設定成為non-blocking,但是,如上圖所示,整個使用者的process其實是一直被block的。只不過process是被select這個函式block,而不是被socket IO給block。

4、非同步IO                                                                                                                                                                        

使用者程序發起read操作之後,立刻就可以開始去做其它的事。而另一方面,從kernel的角度,當它受到一個asynchronous read之後,首先它會立刻返回,所以不會對使用者程序產生任何block。然後,kernel會等待資料準備完成,然後將資料拷貝到使用者記憶體,當這一切都完成之後,kernel會給使用者程序傳送一個signal,告訴它read操作完成了。        

blocking vs non-blocking(看第一階段)

呼叫blocking IO會一直block住對應的程序直到操作完成,而non-blocking IO在kernel還準備資料的情況下會立刻返回。    

synchronous IO vs asynchronous IO(看第二階段)

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。定義中所指的”IO operation”是指真實的IO操作,也即資料從 kernel 搬運到程序的過程。

特別的,我們分析下 non-blocking IO:non-blocking IO在執行recvfrom這個system call的時候,如果kernel的資料沒有準備好,這時候不會block程序。但是,當kernel中資料準備好的時候,recvfrom會將資料從kernel拷貝到使用者記憶體中,這個時候程序是被block了,在這段時間內,程序是被block的。而asynchronous IO則不一樣,當程序發起IO 操作之後,就直接返回再也不理睬了,直到kernel傳送一個訊號,告訴程序說IO完成。在這整個過程中,程序完全沒有被block。

各個IO Model的比較如圖所示:


注意其中的 signal-driven I/O 使用較少,我們不做討論。