天天看點

網絡I/O中的同步、異步、阻塞和非阻塞概念

  在學習網絡程式設計過程中,經常會把這幾個概念搞混淆。

同步I/O與異步I/O差別

  我們先來看一下操作I/O時涉及的對象和步驟(這裡我們以read為例):  

  這裡會涉及到兩個系統對象,一個是調用這個I/O的應用程序(或線程),另一個就是系統核心。當一個read操作發生時,它會經曆兩個階段:1)等待資料就緒 (可讀);2)将資料從核心拷貝到應用程序中 。這兩個階段很重要,因為各種I/O模型的差別就是在這兩個階段上各有不同的情況。

  下邊我們來看一下同步I/O及異步I/O。

  根據UNP一書給出的定義(POSIX):

  • 同步I/O操作(synchronous I/O operation)導緻請求程序阻塞,直到I/O操作完成。UNP第6章中提到的I/O模型——阻塞式I/O模型、非阻塞式I/O模型、I/O複用模型和信号驅動式I/O模型都是同步I/O模型,因為其中真正的I/O操作将阻塞程序。
  • 異步I/O操作(asynchronous I/O operation)不導緻請求程序阻塞。

  可以看出,同步I/O和異步I/O的核心差別在于真正的I/O操作會不會阻塞程序。具體來說,同步I/O需要程序真正地去操作I/O,而異步I/O則由核心在I/O操作完成後再通知應用程序結果。

  我們來看一下UNP一書給出的對5種I/O模型的比較表就可能更清楚了:

  

網絡I/O中的同步、異步、阻塞和非阻塞概念

  由上圖,我們可以知道,除了異步I/O模型,其他模型都會實際阻塞于真正的I/O操作(如recvfrom,這裡真正的I/O操作不包括圖中的‘檢查’)。上圖也說明了非阻塞式I/O雖然在檢查階段不會阻塞,但在檔案描述符就緒(如可讀)的時候是會阻塞的,這是它差別于異步I/O很重要的一點。

  對于同步I/O操作,一個典型的例子就是libevent網絡庫。而對于異步I/O操作,比較有名的例子就是Boost庫的ASIO庫。ACE庫則包括了同步I/O及異步I/O兩種方式。

同步I/O

  上邊我們知道同步I/O操作包括了阻塞式I/O模型、非阻塞式I/O模型、I/O複用模型和信号驅動式I/O。

阻塞式I/O模型

  在Linux中,預設情況下所有的套接字都是阻塞的。以資料報套接字為例,一個典型的讀操作流程大概是這樣:

網絡I/O中的同步、異步、阻塞和非阻塞概念

  程序調用recvfrom,其系統調用直到資料報到達且被複制到應用程序的緩沖區中或者發生錯誤才傳回。最常見的錯誤是系統調用被信号中斷。我們說程序在從調用recvfrom開始到它傳回的整段時間内是被阻塞的。recvfrom成功傳回後,應用程序開始處理資料報。

非阻塞式I/O模型

  程序把一個套接字設定成非阻塞是在通知核心:當所請求的I/O操作非得把本程序投入睡眠才能完成時,不要把本程序投入睡眠,而是傳回一個錯誤。如下例:

網絡I/O中的同步、異步、阻塞和非阻塞概念

  前三次調用recvfrom時沒有資料可傳回,是以核心轉而立即傳回一個EWOULDBLOCK錯誤。第四次調用recvfrom時已有一個資料報準備好,它被複制到應用程序緩沖區,于是recvfrom成功傳回。我們接着處理資料。

  當一個應用程序像這樣對一個非阻塞描述符循環調用recvfrom時,我們稱之為輪詢(polling)。應用程序持續輪詢核心,以檢視某個操作是否就緒。這麼做往往耗費大量CPU時間。

I/O複用模型

  關于I/O複用,知乎上有比較透徹的一個解釋:“關于I/O多路複用(又被稱為“事件驅動”),首先要了解的是,作業系統為你提供了一個功能,當你的某個socket可讀或者可寫的時候,它可以給你一個通知。這樣當配合非阻塞的socket使用時,隻有當系統通知我哪個描述符可讀了,我才去執行read操作,可以保證每次read都能讀到有效資料而不做純傳回-1和EAGAIN的無用功。寫操作類似。作業系統的這個功能通過select/poll/epoll/kqueue之類的系統調用函數來使用,這些函數都可以同時監視多個描述符的讀寫就緒狀況,這樣,多個描述符的I/O操作都能在一個線程内并發交替地順序完成,這就叫I/O多路複用,這裡的“複用”指的是複用同一個線程。”

  有了I/O複用(I/O multiplexing),我們就可以調用select或poll,阻塞在這兩個系統調用中的某一個之上,而不是阻塞在真正的I/O系統調用上。下圖概括展示了I/O複用模型:

網絡I/O中的同步、異步、阻塞和非阻塞概念

  我們阻塞于select調用,等待資料報套接字變為可讀。當select傳回套接字可讀這一條件時,我們調用recvfrom把所讀資料報複制到應用程序緩沖區。

  比較圖6-3和圖6-1,I/O複用并不顯得有什麼優勢,事實上由于使用select需要兩個而不是單個系統調用,I/O複用還稍有劣勢。不過select的優勢在于可以等待多個描述符就緒(與此相對應的方法是多線程+阻塞式I/O,即由每一個線程來調用阻塞式I/O系統調用)。

信号驅動式I/O模型

  我們也可以用信号,讓核心在描述符就緒時發送SIGIO信号給我們。這種模型稱為信号驅動式I/O(signal-driven I/O)。下圖是其概要展示:

網絡I/O中的同步、異步、阻塞和非阻塞概念

  我們首先開啟套接字的信号驅動式I/O功能,并通過sigaction系統調用安裝一個信号處理函數。該系統調用将立即傳回,我們的程序繼續工作, 也就是說它沒有被阻塞。當資料報準備好讀寫時,核心就為該程序産生一個SIGIO信号。我們随後既可以在信号處理函數中調用recvfrom讀取資料報,并通知主循環資料已準備好待處理,也可以立即通知主循環,讓它讀取資料報。

  無論如何處理SIGIO信号,這種模型的優勢在于等待資料報到達期間程序不被阻塞。主循環可以繼續執行,隻要等待來自信号處理函數的通知:既可以是資料已準備好被處理,也可以是資料報已準備好被讀取。

異步I/O

  異步I/O(asynchronous I/O)由POSIX規範定義。一般來說,用于實作異步I/O的函數的工作機制是:告知核心啟動某個操作,并讓核心在整個操作(包括将資料從核心複制到我們自己的緩沖區)完成後通知我們。這種模型與前面介紹的信号驅動模型的主要差別在于:信号驅動式I/O是由核心通知我們何時可以啟動一個I/O操作,而異步I/O模型是由核心通知我們I/O操作何時完成。下圖給出了一個例子:

網絡I/O中的同步、異步、阻塞和非阻塞概念

參考資料

上一篇: 了解inode
下一篇: 補碼的作用