阻塞式I/0 和 非阻塞式I/O 同步異步詳細介紹
請求描述:
`阻塞/非阻塞` 和 `同步/異步` 不是一個概念。舉幾個簡單的例子。當進程調用一個進行IO操作的API時(比如read函數),在數據沒有到達前,read 會掛起,進程會卡住。在數據讀取完畢返回給進程時, read 返回(返回值為讀取到的字節數,數據從內核拷貝到用戶空間),然後進程繼續執行。那麽這次 read 調用,是阻塞的。
非阻塞就是 read 在數據沒有讀取完畢前,就返回了(返回值為-1,errno 設置為 EAGAIN)。此時進程沒有拿到需要的數據。那怎麽辦? 有兩種辦法。
一種是同步:
因為進程沒辦法知道數據什麽時候才真正讀取完畢了,所以需要每隔一段時間就去輪詢一下(就是重新調用 read,看是不是數據真的已經讀取完畢了)。 大部分場景中都不會使用這種方式。但在某些特殊的情況下效率會特別高。一種是異步:
後來的異步實現,就有了更好的 select / poll / epoll(I/O multiplexing)。現在基本上像比較流行的 Nginx / Redis 都用 epoll(在 FreeBSD 上是 kqueue)
圖解:
阻塞式I/O模型:默認情況下,所有套接字都是阻塞的。怎麽理解?先理解這麽個流程,一個輸入操作通常包括兩個不同階段:
(1)等待數據準備好;
(2)從內核向進程復制數據。
對於一個套接字上的輸入操作,第一步通常涉及等待數據從網絡中到達。當所有等待分組到達時,它被復制到內核中的某個緩沖區。第二步就是把數據從內核緩沖區復制到應用程序緩沖區。 好,下面我們以阻塞套接字的recvfrom的的調用圖來說明阻塞
標紅的這部分過程就是阻塞,直到阻塞結束recvfrom才能返回。
非阻塞式I/O: 以下這句話很重要:進程把一個套接字設置成非阻塞是在通知內核,當所請求的I/O操作非得把本進程投入睡眠才能完成時,
不要把進程投入睡眠,而是返回一個錯誤。看看非阻塞的套接字的recvfrom操作如何進行
可以看出recvfrom總是立即返回。
I/O多路復用:雖然I/O多路復用的函數也是阻塞的,但是其與以上兩種還是有不同的,I/O多路復用是阻塞在select,epoll這樣的系統調用之上,
而沒有阻塞在真正的I/O系統調用如recvfrom之上。如圖
信號驅動式I/O:用的很少,就不做講解了。直接上圖
異步I/O:這類函數的工作機制是告知內核啟動某個操作,並讓內核在整個操作(包括將數據從內核拷貝到用戶空間)完成後通知我們。如圖:
註意紅線標記處說明在調用時就可以立馬返回,等函數操作完成會通知我們
例子說明:
例子一: 一個網絡包從應用程序A發到另一臺電腦上的應用程序B,需要經歷:- 從A的業務代碼到A的軟件框架
- 從A的軟件框架到計算機的操作系統內核
- 從A所在計算機的內核到網卡
- 從網卡經過網線發到交換機等設備,層層轉發,到達B所在計算機的網卡
- 從B所在計算機的網卡到B所在計算機的內核
- 從B所在計算機的內核到B的程序的用戶空間
- 從B的軟件框架到B的業務代碼
小樂愛喝茶,廢話不說,煮開水。
出場人物:小樂,水壺兩把(普通水壺,簡稱水壺;會響的水壺,簡稱響水壺)。
1 小樂把水壺放到火上,立等水開。(同步阻塞)
—— 小樂覺得自己有點傻
2 小樂把水壺放到火上,去客廳看電視,時不時去廚房看看水開沒有。(同步非阻塞)
—— 小樂還是覺得自己有點傻,於是變高端了,買了把會響笛的那種水壺。水開之後,能大聲發出嗚嗚~~~~的噪音。
3 小樂把響水壺放到火上,立等水開。(異步阻塞)
—— 小樂覺得這樣傻等意義不大
4 小樂把響水壺放到火上,去客廳看電視,水壺響之前不再去看它了,響了再去拿壺。(異步非阻塞)
—— 小樂覺得自己聰明了。
所謂同步異步,只是對於水壺而言。
普通水壺,同步;響水壺,異步。
雖然都能幹活,但響水壺可以在自己完工之後,提示小樂水開了。這是普通水壺所不能及的。
同步只能讓調用者去輪詢自己(情況2中),造成小樂效率的低下。
所謂阻塞非阻塞,僅僅對於小樂而言。
—— 立等的小樂,阻塞;看電視的小樂,非阻塞。
情況1和情況3中小樂就是阻塞的,媳婦喊他都不知道。
雖然3中響水壺是異步的,可對於立等的小樂沒有太大的意義。
所以一般異步是配合非阻塞使用的,這樣才能發揮異步的效用。
阻塞式I/0 和 非阻塞式I/O 同步異步詳細介紹