1. 程式人生 > >網絡I/O模型---同步異步阻塞非阻塞之惑

網絡I/O模型---同步異步阻塞非阻塞之惑

本質 結果 順序執行 其中 ges package 混合 signal 同時

網絡I/O模型

人多了,就會有問題。web剛出現的時候,光顧的人很少。近年來網絡應用規模逐漸擴大,應用的架構也需要隨之改變。C10k的問題,讓工程師們需要思考服務的性能與應用的並發能力。

網絡應用需要處理的無非就是兩大類問題,網絡I/O數據計算。相對於後者,網絡I/O的延遲,給應用帶來的性能瓶頸大於後者。網絡I/O的模型大致有如下幾種:

  • 同步模型(synchronous I/O)
    • 阻塞I/O(bloking I/O)
    • 非阻塞I/O(non-blocking I/O)
    • 多路復用I/O(multiplexing I/O)
    • 信號驅動式I/O(signal-driven I/O)
  • 異步I/O(asynchronous I/O)

網絡I/O的本質是socket的讀取,socket在linux系統被抽象為流,I/O可以理解為對流的操作。這個操作又分為兩個階段:

  1. 等待流數據準備(wating for the data to be ready)。
  2. 從內核向進程復制數據(copying the data from the kernel to the process)。

對於socket流而已,

  • 第一步通常涉及等待網絡上的數據分組到達,然後被復制到內核的某個緩沖區。
  • 第二步把數據從內核緩沖區復制到應用進程緩沖區。

I/O模型

舉個簡單比喻,來了解這幾種模型。網絡IO好比釣魚,等待魚上鉤就是網絡中等待數據準備好的過程,魚上鉤了,把魚拉上岸就是內核復制數據階段。釣魚的人就是一個應用進程。

阻塞I/O(bloking I/O)

阻塞I/O是最流行的I/O模型。它符合人們最常見的思考邏輯。阻塞就是進程 "被" 休息, CPU處理其它進程去了。在網絡I/O的時候,進程發起recvform系統調用,然後進程就被阻塞了,什麽也不幹,直到數據準備好,並且將數據從內核復制到用戶進程,最後進程再處理數據,在等待數據到處理數據的兩個階段,整個進程都被阻塞。不能處理別的網絡I/O。大致如下圖:

技術分享圖片 1.png

這就好比我們去釣魚,拋竿之後就一直在岸邊等,直到等待魚上鉤。然後再一次拋竿,等待下一條魚上鉤,等待的時候,什麽事情也不做,大概會胡思亂想吧。

阻塞IO的特點就是在IO執行的兩個階段都被block了

非阻塞I/O(non-bloking I/O)

在網絡I/O時候,非阻塞I/O也會進行recvform系統調用,檢查數據是否準備好,與阻塞I/O不一樣,"非阻塞將大的整片時間的阻塞分成N多的小的阻塞, 所以進程不斷地有機會 ‘被‘ CPU光顧"。

也就是說非阻塞的recvform系統調用調用之後,進程並沒有被阻塞,內核馬上返回給進程,如果數據還沒準備好,此時會返回一個error。進程在返回之後,可以幹點別的事情,然後再發起recvform系統調用。重復上面的過程,循環往復的進行recvform系統調用。這個過程通常被稱之為輪詢。輪詢檢查內核數據,直到數據準備好,再拷貝數據到進程,進行數據處理。需要註意,拷貝數據整個過程,進程仍然是屬於阻塞的狀態。

技術分享圖片 2.png

我們再用釣魚的方式來類別,當我們拋竿入水之後,就看下魚漂是否有動靜,如果沒有魚上鉤,就去幹點別的事情,比如再挖幾條蚯蚓。然後不久又來看看魚漂是否有魚上鉤。這樣往返的檢查又離開,直到魚上鉤,再進行處理。

非阻塞 IO的特點是用戶進程需要不斷的主動詢問kernel數據是否準備好。

多路復用I/O(multiplexing I/O)

可以看出,由於非阻塞的調用,輪詢占據了很大一部分過程,輪詢會消耗大量的CPU時間。結合前面兩種模式。如果輪詢不是進程的用戶態,而是有人幫忙就好了。多路復用正好處理這樣的問題。

多路復用有兩個特別的系統調用selectpoll。select調用是內核級別的,select輪詢相對非阻塞的輪詢的區別在於---前者可以等待多個socket,當其中任何一個socket的數據準好了,就能返回進行可讀,然後進程再進行recvform系統調用,將數據由內核拷貝到用戶進程,當然這個過程是阻塞的。多路復用有兩種阻塞,select或poll調用之後,會阻塞進程,與第一種阻塞不同在於,此時的select不是等到socket數據全部到達再處理, 而是有了一部分數據就會調用用戶進程來處理。如何知道有一部分數據到達了呢?監視的事情交給了內核,內核負責數據到達的處理。也可以理解為"非阻塞"吧。

技術分享圖片 3.png

對於多路復用,也就是輪詢多個socket。釣魚的時候,我們雇了一個幫手,他可以同時拋下多個釣魚竿,任何一桿的魚一上鉤,他就會拉桿。他只負責幫我們釣魚,並不會幫我們處理,所以我們還得在一幫等著,等他把收桿。我們再處理魚。多路復用既然可以處理多個I/O,也就帶來了新的問題,多個I/O之間的順序變得不確定了,當然也可以針對不同的編號。

多路復用的特點是通過一種機制一個進程能同時等待IO文件描述符,內核監視這些文件描述符(套接字描述符),其中的任意一個進入讀就緒狀態,select, poll,epoll函數就可以返回。對於監視的方式,又可以分為 select, poll, epoll三種方式。

了解了前面三種模式,在用戶進程進行系統調用的時候,他們在等待數據到來的時候,處理的方式不一樣,直接等待,輪詢,select或poll輪詢,第一個過程有的阻塞,有的不阻塞,有的可以阻塞又可以不阻塞。當時第二個過程都是阻塞的。從整個I/O過程來看,他們都是順序執行的,因此可以歸為同步模型(asynchronous)。都是進程主動向內核檢查。

異步I/O(asynchronous I/O)

相對於同步I/O,異步I/O不是順序執行。用戶進程進行aio_read系統調用之後,無論內核數據是否準備好,都會直接返回給用戶進程,然後用戶態進程可以去做別的事情。等到socket數據準備好了,內核直接復制數據給進程,然後從內核向進程發送通知。I/O兩個階段,進程都是非阻塞的。

技術分享圖片 4.png

比之前的釣魚方式不一樣,這一次我們雇了一個釣魚高手。他不僅會釣魚,還會在魚上鉤之後給我們發短信,通知我們魚已經準備好了。我們只要委托他去拋竿,然後就能跑去幹別的事情了,直到他的短信。我們再回來處理已經上岸的魚。

同步和異步的區別

通過對上述幾種模型的討論,需要區分阻塞和非阻塞,同步和異步。他們其實是兩組概念。區別前一組比較容易,後一種往往容易和前面混合。對於同步和異步而言,往往是一個函數調用之後,是否直接返回結果,如果函數掛起,直到獲得結果,這是同步;如果函數馬上返回,等數據到達再通知函數,那麽這是異步的路程。

至於阻塞和非阻塞,則是函數是否讓線程掛起不再往下執行。通常同步阻塞,異步非阻塞。什麽情況下是異步阻塞呢?即函數調用之後並沒有返回結果而註冊了回調函數,非阻塞的情況下,函數也馬上返回,可是如果此時函數不返回,那麽此時就是阻塞的狀態,等數據到達通知函數,依然是異步的過程。

區分阻塞和非阻塞只要區分函數調用之後是否掛起返回就可以了,區分異步和同步,則是函數調用之後,數據或條件滿足之後如何通知函數。等待數據返回則是同步,通過回調則是異步。

技術分享圖片 5.png

對於同步模型,主要是第一階段處理方法不一樣。而異步模型,兩個階段都不一樣。這裏我們忽略了信號驅動模式。這幾個名詞還是容易讓人迷惑。

本文所討論的IO模型來自大名鼎鼎的《unix網絡編程:卷1套接字聯網API》。單臺服務器中的linux系統。分布式的環境或許會不一樣。個人學習筆記,參考了網絡上大多數文章,做了一點小測試

copy自:https://www.jianshu.com/p/55eb83d60ab1

網絡I/O模型---同步異步阻塞非阻塞之惑