UNIX網絡編程1 理解同步、阻塞、非阻塞、異步網絡I/O

本文側重的是網絡I/O,如blocking、non-blocking、I/O multiplexing(event driven I/O)、signal-driven(不常用)、 asynchronous這幾種UNIX網絡編程中提到的模型。
wiki中的Asynchronous I/O認爲,asynchronous I/O和non-blocking I/O是一個意思,都是指一種I/O處理形式:在傳輸完成之前,允許用戶進程去繼續執行其他的事情。synchronous I/O或blocking I/O在通信進行時會阻塞用戶程序的執行,等待I/O操作的完成。
stackoverflow中有人這樣描述:blocking和synchronous的意思是,當調用API時,當前線程會被掛起,直到有結果返回;Non-blocking則是如果結果還沒有準備好,調用的API立馬返回一個error並且不再做任何事情。因此你必須用一些相關方法來查詢該API是否準備好被調用(最好是以一種高效的方式模仿等待,而避免在一個緊湊的循環中手動polling輪詢);Asynchronous的意思是調用的API總是立即返回,而且後臺還在完成請求工作,因此必定有一些方法來獲得結果,這和non-blocking是有區別的地方。
還有一種說法,同步和異步是針對應用程序和內核交互而言的,同步過程中進程觸發IO操作並等待或者輪詢的去查看IO操作是否完成。異步過程中進程觸發IO操作以後,直接返回,做自己的事情,IO交給內核來處理,完成後內核通知進程IO完成。
綜合各種說法,我們可以得出結論:Asynchronous I/O異步在整個I/O過程發出請求的用戶進程都不阻塞,所以肯定是非阻塞。阻塞和非阻塞可以理解爲進行一個調用時,這個調用能不能立即返回,non-blocking模式下recvfrom可以立即返回,所以在內核檢查數據未準備好時這個調用是非阻塞的,多路複用(select、poll、epoll)對每個socket來說都設置成non-blocking,select函數本身通過指定timeout是0秒0毫秒變成非阻塞函數,而拷貝數據階段大家都是阻塞的,用戶應用進程和內核是同步的。所以,同步模型肯定有阻塞的過程(拷貝數據階段),也有可能有非阻塞調用。於是乎,select就變成了同步非阻塞模型。

對於一個網絡I/O比如read,涉及到用戶空間的應用程序進程或線程和系統內核,當一個read操作發生時,它會經歷兩個階段:
1)等待數據準備
2)將數據從內核拷貝到進程中
按照這兩個階段,以recvfrom對比blocking、non-blocking、multiplexing、asynchronous,詳見UNIX網絡編程卷1第6章。
blocking I/O:
在linux中,默認情況下所有的socket都是blocking,一個典型的讀操作流程大概是這樣,不管是在kernel等待數據準備階段,還是在從內核複製數據到用戶進程階段,應用程序都是blocking:


non-blocking I/O:
在linux下,可以通過設置socket使其變爲non-blocking,對一個non-blocking socket執行讀操作,當kernel還在等待數據準備好時,用戶應用程序調用recvfrom系統調用則會立即返回一個錯誤EWOULDBLOCK,可以循環調用recvfrom(輪詢)不斷查詢內核看數據是否準備好,這個階段用戶應用進程是non-blocking的:

I/O multiplexing:
在linux中,調用select或poll系統調用,用戶應用程序就在這兩個系統調用上阻塞blocking,而不是阻塞於真正的I/O系統調用。應用程序阻塞於select調用,等待socket可讀,當select返回socket可讀條件時,應用程序就可以調用recvfrom將數據報從內核拷貝到應用空間中。

和blocking I/O對比,其實在內核等待數據準備階段和從內核拷貝數據到用戶空間階段,應用程序都blocking了,只不過multiplexing在第一個階段是阻塞與select系統調用。在處理單個連接時,由於multiplexing使用了兩個系統調用,實際上比直接blocking I/O性能還差,但multiplexing的目的並不是處理單個連接,二是同時處理多個連接(連接數較少時,使用多線程+直接blocking延遲可能更低)。
在multiplexing中,對於每一個socket一般都設置成non-blocking(即select再往下調用,詢問單個socket的數據是否準備好時是non-blocking的),但用戶應用程序是一直blocking的,只不過不是被I/O系統調用所block。

asynchronous I/O:
用戶應用程序發起aio_read操作之後,就立即可以去做其他的事情。在kernel中,當它收到一個aio_read後,首先會立刻返回所以不會對用戶進程產生任何block。然後kernel等待數據準備完成,然後將數據拷貝到用戶空間,當這兩個階段都完成後,kernel會給應用進程發送一個siignal,告訴它read操作完成了。


比較四種模型,blocking、non-block、multiplexing、signal(未討論)主要區別是在第一階段,第二階段基本相同,在數據從內核拷貝到調用者的緩衝區時,進程阻塞於recvfrom調用。而異步I/O模型處理的兩個階段都不同於前四個模型。來源於UNIX網絡編程第6張圖6.6:

我們認爲,blocking、non-blocking、multiplexing(event-driven)、signal-driven I/O模型和asynchronous是有明顯區別的,即在I/O操作第二階段會阻塞,所以認爲這四種都是同步I/O模型。
另外Posix.1定義同步和異步I/O爲:
  • 同步I/O操作引起請求進程阻塞,直到I/O操作完成。non-blocking I/O的recvfrom調用在數據沒準備好時立即返回這是non-blocking的效果,對於select/poll也一樣,但是在拷貝數據階段都不得不阻塞用戶進程。
  • 異步I/O操作不引起請求進程阻塞,調用aio_read後立即返回,請求進程可以去幹別的事情,內核完成I/O操作後向請求進程遞交在aio_read中指定的信號,用戶應用程序還得有信號處理程序來另外處理信號從而接收數據報。
參考:
《UNIX網絡編程第1卷 第6章》
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章