阻塞IO、非阻塞IO、同步IO,非同步IO
再說一下IO發生時涉及的物件和步驟。對於一個network IO (這裡我們以read舉例),它會涉及到兩個系統物件,一個是呼叫這個IO的process (or thread),另一個就是系統核心(kernel)。當一個read操作發生時,該操作會經歷兩個階段:
#1)等待資料準備 (Waiting for the data to be ready) #2)將資料從核心拷貝到程序中(Copying the data from the kernel to the process)
記住這兩點很重要,因為這些IO模型的區別就是在兩個階段上各有不同的情況。
補充:
#1、輸入操作:read、readv、recv、recvfrom、recvmsg共5個函式,如果會阻塞狀態,則會經理wait data和copy data兩個階段,如果設定為非阻塞則在wait 不到data時丟擲異常 #2、輸出操作:write、writev、send、sendto、sendmsg共5個函式,在傳送緩衝區滿了會阻塞在原地,如果設定為非阻塞,則會丟擲異常 #3、接收外來連結:accept,與輸入操作類似 #4、發起外出連結:connect,與輸出操作類似
二 阻塞IO(blocking IO)
過程:
當用戶呼叫了recvfrom這個系統呼叫時,kernal開始第一個階段,準備資料,在資料還未準備好時不會反悔任何東西,使用者會被阻塞在recvfrom,直到資料準備完畢並且從kernal中把資料拷貝到使用者記憶體後才不阻塞,在此之前使用者只能一直等待。
幾乎所有的程式設計師第一次接觸到的網路程式設計都是從listen()、send()、recv() 等介面開始的,使用這些介面可以很方便的構建伺服器/客戶機的模型。然而大部分的socket介面都是阻塞型的。
ps:所謂阻塞型介面是指系統呼叫(一般是IO介面)不返回呼叫結果並讓當前執行緒一直阻塞,只有當該系統呼叫獲得結果或者超時出錯時才返回。
三 非阻塞IO(non-blocking IO)
過程:
當用戶呼叫了recvfrom這個系統呼叫時,kernal開始第一個階段,準備資料,在資料還未準備好時,會返回一個error,不會阻塞使用者程序,此時使用者可以做其他事,每隔一段時間詢問資料是否準備好,當收到資料準備好後,則進入copydata階段,既把資料從kernal拷貝到使用者記憶體中,但是這個拷貝的過程使用者仍然是被阻塞的,如果遇到資料量大的情況,使用者會被長時間阻塞。
所以,在非阻塞式IO中,使用者程序其實是需要不斷的主動詢問kernel資料準備好了沒有。
缺點:1、會迴圈呼叫recvfrom,大幅增大cpu的佔用率
2、任務完成的響應延時增大,任務可能會夾在兩個輪詢之間,降低資料的吞吐量。
四 多路複用IO(IO multiplexing)
有些地方也稱這種IO方式為事件驅動IO(event driven IO)。我們都知道,select/epoll的好處就在於單個process就可以同時處理多個網路連線的IO。它的基本原理就是select/epoll這個function會不斷的輪詢所負責的所有socket,當某個socket有資料到達了,就通知使用者程序。它的流程如圖:
當用戶程序呼叫了select,那麼整個程序會被block,而同時,kernel會“監視”所有select負責的socket,當任何一個socket中的資料準備好了,select就會返回。這個時候使用者程序再呼叫read操作,將資料從kernel拷貝到使用者程序。 這個圖和blocking IO的圖其實並沒有太大的不同,事實上還更差一些。因為這裡需要使用兩個系統呼叫(select和recvfrom),而blocking IO只調用了一個系統呼叫(recvfrom)。但是,用select的優勢在於它可以同時處理多個connection。
強調:
1. 如果處理的連線數不是很高的話,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server效能更好,可能延遲還更大。select/epoll的優勢並不是對於單個連線能處理得更快,而是在於能處理更多的連線。
2. 在多路複用模型中,對於每一個socket,一般都設定成為non-blocking,但是,如上圖所示,整個使用者的process其實是一直被block的。只不過process是被select這個函式block,而不是被socket IO給block。
結論: select的優勢在於可以處理多個連線,不適用於單個連線
五 非同步IO(Asynchronous I/O)
當用戶發起recvfrom指令後,直接做其他事,當資料準備好後,會自動把資料從kernal拷貝到使用者記憶體,之後再發一個完成的訊號,讀取完成。