OS / 5 种 IO 模型
零、從 TCP 發送數據的流程說起
要深入的理解各種 IO 模型,那么必須先了解下產生各種 IO 的原因是什么,要知道這其中的本質問題那么我們就必須要知一條消息是如何從過一個人發送到另外一個人的;
以兩個應用程序通訊為例,我們來了解一下當“A”向"B" 發送一條消息,簡單來說會經過如下流程:
第一步:應用 A 把消息發送到 TCP 發送緩沖區。
第二步: TCP 發送緩沖區再把消息發送出去,經過網絡傳遞后,消息會發送到 B 服務器的 TCP 接收緩沖區。
第三步:B 再從 TCP 接收緩沖區去讀取屬于自己的數據。
根據上圖我們基本上了解消息發送要經過應用 A、應用 A 對應服務器的 TCP 發送緩沖區、經過網絡傳輸后消息發送到了應用 B 對應服務器 TCP 接收緩沖區、然后最終 B 應用讀取到消息。
如果理解了上面的消息發送流程,那么我們下面開始進入文章的主題;
一、阻塞 IO | 非阻塞 IO
我們把視角切換到上面圖中的第三步, 也就是應用 B 從 TCP 緩沖區中讀取數據。
思考一個問題:
因為應用之間發送消息是間斷性的,也就是說在上圖中 TCP 緩沖區還沒有接收到屬于應用 B 該讀取的消息時,那么此時應用 B 向 TCP 緩沖區發起讀取申請,TCP 接收緩沖區是應該馬上告訴應用 B 現在沒有你的數據,還是說讓應用 B 在這里等著,直到有數據再把數據交給應用 B 。
把這個問題應用到第一個步驟也是一樣,應用 A 在向 TCP 發送緩沖區發送數據時,如果 TCP 發送緩沖區已經滿了,那么是告訴應用 A 現在沒空間了,還是讓應用 A 等待著,等 TCP 發送緩沖區有空間了再把應用 A 的數據訪拷貝到發送緩沖區。
什么是阻塞 IO
如果上面的問題你已經思考過了,那么其實你已經明白了什么是阻塞 IO 了,所謂阻塞 IO 就是當應用 B 發起讀取數據申請時,在內核數據沒有準備好之前,應用 B 會一直處于等待數據狀態,直到內核把數據準備好了交給應用B才結束。
術語描述:在應用調用 recvfrom 讀取數據時,其系統調用知道數據包到達切被復制到應用緩沖區中或者發送錯誤時才返回,在此期間一直會等待,進程從調用到返回這段時間內都是被阻塞的成為阻塞 IO。
流程:
什么是非阻塞 IO
我敢保證如果你已經理解了阻塞 IO,那么必定已經知道了什么是非阻塞 IO。按照上面的思路,所謂非阻塞 IO 就是當應用 B 發起讀取數據申請時,如果內核數據沒有準備好會即刻告訴應用 B,不會讓 B 在這里等待。
術語:非阻塞 IO 是在應用調用 recvfrom 讀取數據時,如果該緩沖區沒有數據的話,就會直接返回一個 EWOULDBLOCK 錯誤,不會讓應用一直等待中。在沒有數據的時候會即刻返回錯誤標識,那也意味著如果應用要讀取數據就需要不斷的調用 recvfrom 請求,直到讀取到它數據要的數據為止。
流程:
?
二、IO 復用模型
如果你已經明白了非阻塞 IO 的工作模式,那么接下來我們繼續了解 IO 復用模型的產生原因和思路。
思考一個問題:
我們還是把視角放到應用 B 從 TCP 緩沖區中讀取數據這個環節來。如果在并發的環境下,可能會 N 個人向應用 B 發送消息,這種情況下我們的應用就必須創建多個線程去讀取數據,每個線程都會自己調用recvfrom 去讀取數據。那么此時情況可能如下圖:
如上圖一樣,并發情況下服務器很可能一瞬間會收到幾十上百萬的請求,這種情況下應用 B 就需要創建幾十上百萬的線程去讀取數據,同時又因為應用線程是不知道什么時候會有數據讀取,為了保證消息能及時讀取到,那么這些線程自己必須不斷的向內核發送 recvfrom 請求來讀取數據;
那么問題來了,這么多的線程不斷調用 recvfrom 請求數據,先不說服務器能不能扛得住這么多線程,就算扛得住那么很明顯這種方式是不是太浪費資源了,線程是我們操作系統的寶貴資源,大量的線程用來去讀取數據了,那么就意味著能做其它事情的線程就會少。
所以,有人就提出了一個思路,能不能提供一種方式,可以由一個線程監控多個網絡請求(我們后面將稱為 fd 文件描述符,linux 系統把所有網絡請求以一個 fd 來標識),這樣就可以只需要一個或幾個線程就可以完成數據狀態詢問的操作,當有數據準備就緒之后再分配對應的線程去讀取數據,這么做就可以節省出大量的線程資源出來,這個就是 IO 復用模型的思路。
正如上圖,IO 復用模型的思路就是系統提供了一種函數可以同時監控多個 fd 的操作,這個函數就是我們常說到的 select、poll、epoll 函數,有了這個函數后,應用線程通過調用 select 函數就可以同時監控多個 fd,select 函數監控的 fd 中只要有任何一個數據狀態準備就緒了,select 函數就會返回可讀狀態,這時詢問線程再去通知處理數據的線程,對應線程此時再發起 recvfrom 請求去讀取數據。
術語描述:進程通過將一個或多個 fd 傳遞給select,阻塞在 select 操作上,select 幫我們偵測多個 fd 是否準備就緒,當有 fd 準備就緒時,select 返回數據可讀狀態,應用程序再調用 recvfrom 讀取數據。
總結:復用IO的基本思路就是通過 select 或 poll、epoll 來監控多 fd ,來達到不必為每個fd創建一個對應的監控線程,從而減少線程資源創建的目的。
三、信號驅動 IO 模型
復用 IO 模型解決了一個線程可以監控多個 fd 的問題,但是 select 是采用輪詢的方式來監控多個 fd 的,通過不斷的輪詢 fd 的可讀狀態來知道是否就可讀的數據,而無腦的輪詢就顯得有點暴力,因為大部分情況下的輪詢都是無效的,所以有人就想,能不能不要我總是去問你是否數據準備就緒,能不能我發出請求后等你數據準備好了就通知我,所以就衍生了信號驅動 IO 模型。
于是信號驅動 IO 不是用循環請求詢問的方式去監控數據就緒狀態,而是在調用 sigaction 時候建立一個 SIGIO 的信號聯系,當內核數據準備好之后再通過 SIGIO 信號通知線程數據準備好后的可讀狀態,當線程收到可讀狀態的信號后,此時再向內核發起 recvfrom 讀取數據的請求,因為信號驅動 IO 的模型下應用線程在發出信號監控后即可返回,不會阻塞,所以這樣的方式下,一個應用線程也可以同時監控多個fd。
類似于下圖描述:
術語描述:首先開啟套接口信號驅動 IO 功能,并通過系統調用 sigaction 執行一個信號處理函數,此時請求即刻返回,當數據準備就緒時,就生成對應進程的 SIGIO 信號,通過信號回調通知應用線程調用recvfrom 來讀取數據。
總結: IO 復用模型里面的 select 雖然可以監控多個 fd 了,但 select 其實現的本質上還是通過不斷的輪詢 fd 來監控數據狀態, 因為大部分輪詢請求其實都是無效的,所以信號驅動 IO 意在通過這種建立信號關聯的方式,實現了發出請求后只需要等待數據就緒的通知即可,這樣就可以避免大量無效的數據狀態輪詢操作。
四、異步 IO
其實經過了上面兩個模型的優化,我們的效率有了很大的提升,但是我們當然不會就這樣滿足了,有沒有更好的辦法,通過觀察我們發現,不管是 IO 復用還是信號驅動,我們要讀取一個數據總是要發起兩階段的請求,第一次發送 select 請求,詢問數據狀態是否準備好,第二次發送 recevform 請求讀取數據。
思考一個問題:
也許你一開始就有一個疑問,為什么我們明明是想讀取數據,什么非得要先發起一個 select 詢問數據狀態的請求,然后再發起真正的讀取數據請求,能不能有一種一勞永逸的方式,我只要發送一個請求我告訴內核我要讀取數據,然后我就什么都不管了,然后內核去幫我去完成剩下的所有事情?
當然既然你想得出來,那么就會有人做得到,有人設計了一種方案,應用只需要向內核發送一個 read 請求,告訴內核它要讀取數據后即刻返回;內核收到請求后會建立一個信號聯系,當數據準備就緒,內核會主動把數據從內核復制到用戶空間,等所有操作都完成之后,內核會發起一個通知告訴應用,我們稱這種一勞永逸的模式為異步IO模型。
術語描述: 應用告知內核啟動某個操作,并讓內核在整個操作完成之后,通知應用,這種模型與信號驅動模型的主要區別在于,信號驅動 IO 只是由內核通知我們合適可以開始下一個 IO 操作,而異步 IO 模型是由內核通知我們操作什么時候完成。
總結:異步 IO 的優化思路是解決了應用程序需要先后發送詢問請求、發送接收數據請求兩個階段的模式,在異步 IO 的模式下,只需要向內核發送一次請求就可以完成狀態詢問和數拷貝的所有操作。
再談 IO 模型里面的同步異步
我們通常會說到同步阻塞 IO、同步非阻塞 IO,異步 IO 幾種術語,通過上面的內容,那么我想你現在肯定已經理解了什么是阻塞什么是非阻塞了,所謂阻塞就是發起讀取數據請求的時,當數據還沒準備就緒的時候,這時請求是即刻返回,還是在這里等待數據的就緒,如果需要等待的話就是阻塞,反之如果即刻返回就是非阻塞。
我們區分了阻塞和非阻塞后再來分別下同步和異步,在 IO 模型里面如果請求方從發起請求到數據最后完成的這一段過程中都需要自己參與,那么這種我們稱為同步請求;反之,如果應用發送完指令后就不再參與過程了,只需要等待最終完成結果的通知,那么這就屬于異步。
我們再看同步阻塞、同步非阻塞,他們不同的只是發起讀取請求的時候一個請求阻塞,一個請求不阻塞,但是相同的是,他們都需要應用自己監控整個數據完成的過程。而為什么之后異步非阻塞而沒有異步阻塞呢,因為異步模型下請求指定發送完后就即刻返回了,沒有任何后續流程了,所以它注定不會阻塞,所以也就只會有異步非阻塞模型了。
?
轉載于:https://zhuanlan.zhihu.com/p/115912936
?
(SAW:Game Over!)
總結
以上是生活随笔為你收集整理的OS / 5 种 IO 模型的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 数据库 / 事务的隔离级别
- 下一篇: 数据库 / 各种锁