用通俗易懂的方式講IO的五種模型

千鋒Python唐小強發表於2020-07-14

我們經常提到 IO、NIO 這些名詞。那麼,到底什麼是 IO 呢?什麼又是 NIO 呢?

另外,我們平時又會聽到兩組很相似的概念:阻塞 / 非阻塞、同步 / 非同步。那麼,阻塞和非阻塞有什麼區別呢?同步和非同步又有什麼區別呢?很多同學對這兩組概念都比較容易混淆,也講不清楚。

所以,本期教程就從網路 IO 的角度出發並用生活中常見的案例來白話 IO 的五種模型,以及上面兩組概念。

使用者空間和核心空間

作業系統的核心是核心,它獨立於普通的應用程式,可以訪問受保護的核心空間,也有訪問底層硬體裝置的所有許可權。為了保護核心的安全,現在作業系統一般都強制使用者程式不能直接操作核心,所以作業系統把記憶體空間劃分成了兩個部分:核心空間和使用者空間。

用通俗易懂的方式講IO的五種模型

這就好比,飯店老闆把整個飯店劃分成兩個部分:大廳和廚房。大廳用於顧客吃飯,廚房用於廚師做飯,廚房的門上面一般還會寫著:“廚房重地,閒人免進”,也就是顧客一般不具有直接使用廚房的特性。

用通俗易懂的方式講IO的五種模型

所以,當我們使用 TCP 傳送資料的時候,需要先將資料從使用者空間複製到核心空間,再由核心操作將資料從核心空間傳送出去;當我們使用 TCP 讀取資料的時候,資料先在核心空間準備好,再從核心空間複製到使用者空間供使用者程式使用。

用通俗易懂的方式講IO的五種模型

這就好比,當我們在飯店吃飯的時候,先在客廳點好菜,再由服務員把我們的選單傳遞進廚房;當廚房做好了菜,再從廚房由服務員傳遞到客廳一樣。

所以,一次 IO 的讀取操作分為兩個階段(寫入操作類似):

  • 等待核心空間資料準備階段
  • 資料從核心空間複製到使用者空間

為此,Unix 根據這兩個階段又把 IO 分成了以下五種 IO 模型:

  • 阻塞型 IO
  • 非阻塞型 IO
  • IO 多路複用
  • 訊號驅動 IO
  • 非同步 IO

下面我們一一道來。

阻塞型 IO

阻塞型 IO,即當使用者程式發起請求時,一直阻塞直到資料複製到使用者空間為止才返回。

阻塞型 IO 在兩個階段是連續阻塞著的,直到資料返回。

用通俗易懂的方式講IO的五種模型

這就好比,你去路邊買快餐,這家店比較低階,只有一輛車一個老闆。點完餐後,你傻傻地看著老闆開始打菜,然後拿給你。整個過程中,你只能看著老闆打完菜並拿給你,這兩個階段你都是阻塞的。

用通俗易懂的方式講IO的五種模型

非阻塞型 IO

非阻塞型 IO,使用者程式不斷詢問核心,資料準備好了嗎?一直重試,直到核心說資料準備好了,然後把資料從核心空間複製到使用者空間,返回成功,開始處理資料。

非阻塞型 IO 第一階段不阻塞,第二階段阻塞。

用通俗易懂的方式講IO的五種模型

這就好比,你去小炒店,這家店高階一點,有獨立的店面。點完餐後,你可以邊玩手機邊等。隔了一會你跑過去問一下老闆 “我的菜好了沒”,老闆說 “還沒好”;隔一會你又跑過去問了下 “我的菜好了沒”,老闆說 “還沒有”;幾次後,你又說 “老闆,我的菜好了沒”,老闆說 “來了來了”,然後你看著他把菜端到你面前。整個過程中,詢問 “菜好了沒” 你不用阻塞,老闆立即回應你,你可以立即玩手機,但是端菜的時候你是傻傻地看著他端的,這期間你無法玩手機,你是阻塞的。

用通俗易懂的方式講IO的五種模型

IO 多路複用

IO 多路複用,多個 IO 操作共同使用一個 selector(選擇器)去詢問哪些 IO 準備好了,selector 負責通知那些資料準備好了的 IO,它們再自己去請求核心資料。

IO 多路複用,第一階段會阻塞在 selector 上,第二階段複製資料也會阻塞。

用通俗易懂的方式講IO的五種模型

這就好比,你去川菜館吃飯,這家飯店比較大,人也多,還有個漂亮的服務員。你點完菜後,勾搭了一下服務員 “美女,我點個辣子雞丁,好了通知我一下哦”,美女也沒搭理你。其它人也是這麼勾搭美女的。然後,美女忙得不可開交,隔一會去廚房看一下,哪些菜好了,每次出來,都會喊 “那誰誰誰,你的啥啥菜好了,自己過來端一下。”。整個過程中,美女去廚房看菜是阻塞的,因為沒有菜好的時候她還要等一會;你跑過去端菜也是阻塞的。一部分阻塞在美女身上,一部分阻塞在你身上。

用通俗易懂的方式講IO的五種模型

訊號驅動 IO

訊號驅動 IO,使用者程式發起讀取請求之前先註冊一個訊號給核心說明自己需要什麼資料,這個註冊請求立即返回,等核心資料準備好了,主動通知使用者程式,使用者程式再去請求讀取資料,此時,需要等待資料從核心空間複製到使用者空間再返回。

訊號驅動,第一階段不阻塞,第二階段阻塞。

用通俗易懂的方式講IO的五種模型

這就好比,你去 “金拱門” 吃麥當勞一樣。你在旁邊的機器上點完餐後出來一張小票 “1024 號”,然後你邊玩手機邊等。過了一會,喇叭喊,“1024 號,請取餐。1024 號,請取餐。”,然後,你屁顛屁顛地跑過去取餐。整個過程中,點餐是立即返回的,之後想幹啥幹啥,不阻塞(也就是說你不用傻等著餐做好);取餐的過程你需要從櫃檯端到你的位置上,是阻塞的。

用通俗易懂的方式講IO的五種模型

非同步 IO

非同步 IO,使用者程式發起讀取請求後立馬返回,當資料完全複製到使用者空間後通知使用者直接使用資料。

非同步 IO,兩個階段都不阻塞。

用通俗易懂的方式講IO的五種模型

這就好比,你去吃 “漁粉”。掃碼點餐後,你完全不用管,過了一會,一個大媽把飯菜端到你面前,還貼心地說了句 “客官,請慢用”,然後你幸福地吃下了這碗 “金湯漁粉”。整個過程中,你既不用傻等著漁粉做好,也不用看著大媽把菜端到你面前或者你自己去端,完全不阻塞,純非同步。所以,這種體驗是最好的。

用通俗易懂的方式講IO的五種模型

所以,如果把吃飯的過程分成兩個部分:“準備飯菜” 和 “端菜”,那麼:

  1. 如果你傻等著兩個階段完成,就是阻塞 IO;
  2. 如果你隔一會詢問一下 “菜做好了沒”,期間你可以玩手機,但是端菜的時候你傻傻地看著老闆端過來,就是非阻塞 IO;
  3. 如果你和其他人都委託服務員幫你們隔一會看一下 “菜做好了沒”,但是端菜需要自己去端,就是 IO 多路複用;
  4. 如果是機器點餐,機器喊話取餐,就是訊號驅動 IO;
  5. 如果是掃碼點餐,自動上餐,就是非同步 IO;

阻塞與非阻塞

阻塞,是指呼叫結果返回之前,當前執行緒會被掛起,直到呼叫結果返回。比如,你傻等著端菜結束,你就是阻塞的。

非阻塞,是指不能立即得到結果之前,當前執行緒不被掛起,而是可以繼續做其它的事。比如,你邊玩手機邊等飯菜準備好,你就是非阻塞的。

簡單點,就是阻塞呼叫你必須掛起傻等著結果返回,非阻塞呼叫你不關心結果,呼叫之後你愛幹嘛幹嘛。

同步與非同步

關於同步與非同步,我們直接看看 POSIX 中的定義:

A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes;

An asynchronous I/O operation does not cause the requesting process to be blocked;

同步,呼叫者會被阻塞直到 IO 操作完成,呼叫的結果隨著請求的結束而返回。

非同步,呼叫者不會被阻塞,呼叫的結果不隨著請求的結束而返回,而是透過通知或回撥函式的形式返回。

阻塞 / 非阻塞,更關心的是當前執行緒是不是被掛起。

同步 / 非同步,更關心的是呼叫結果是不是隨著請求結束而返回。

這裡的阻塞是指整個 IO 過程中是否有阻塞,更確切地說是 recvfrom 這個系統呼叫是否會阻塞,在我們的案例中,可以理解為 “端菜” 這個行為對於你來說是不是阻塞的。

所以,阻塞型 IO、非阻塞型、IO 多路複用、訊號驅動 IO 都是同步 IO,只有最後一種才是非同步 IO。

為什麼不選擇非同步 IO?

透過上面的分析,非同步 IO 才是最牛的 IO 模型,那麼,我們為什麼不選擇非同步 IO 呢?

那是因為非同步 IO 在 linux 上還不成熟,而我們的伺服器通常都是 linux,所以現在大部分框架都不是很支援非同步 IO,包括 Netty 之前實現了一版,但是後面給廢棄掉了。

本節透過飯店的不同模型來描述了 IO 的五種模型,你們肯定也有了清晰的認識和深刻的理解,今天吃飯的時候,你處於哪種模型呢?


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69923331/viewspace-2704526/,如需轉載,請註明出處,否則將追究法律責任。

相關文章