【網路IO系列】IO的五種模型,BIO、NIO、AIO、IO多路複用、 訊號驅動IO

穿黑風衣的牛奶發表於2021-07-18

前言

在上一篇文章中,我們瞭解了作業系統中核心程式和使用者程式之間的區別和聯絡,還提到了核心空間和使用者空間,當我們需要讀取一條資料的時候,首先需要發請求告訴核心,我需要什麼資料,等核心準備好資料之後再從核心空間拷貝到使用者空間 注意加粗的部分,這兩個階段至關重要

對以上的兩個過程以及作業系統的IO流程不瞭解的,請務必左轉去看上一篇文章,上篇文章中是學習IO的基礎知識,只有把上一篇文章的內容看懂了,對於後續的IO幾種模型的學習和理解才會更為深刻,上一篇文章可以說是整個IO中的基石級別的知識。

文章連結 【網路IO系列】 預備知識 作業系統之核心程式和使用者程式

IO的五種模型

我們回到正題,從上篇文章我們知道,當我們進行一次IO的時候,是要經過這兩個階段的,分別是

第一階段 :等待核心準備資料

第二階段:資料從核心空間拷貝到使用者空間

這兩個階段則決定著IO的各種模型的型別,通過這兩個階段,可以將IO模型分成五種,分別是

  • 阻塞式IO(BIO)
  • 非阻塞式IO(NIO)
  • IO多路複用
  • 訊號驅動IO
  • 全非同步IO(AIO)

阻塞

說到阻塞,在這裡想先明確一下,什麼是阻塞。從執行緒或者程式的角度來看,阻塞就是因為當前執行的這個執行緒,暫時的失去了CPU的執行權,被掛起等待下一次執行緒的排程或者執行緒被喚醒。如果是具體到我們的IO的話,則可以理解為,阻塞的時候你需要等待,等到資料準備好或者執行結果返回,才能繼續下一步的操作,不然只能一直等待下去。而非阻塞,則是,即便資料沒有準備好,或者執行沒有完成,你也可以去做其他的事情。

舉個例子,比如說你出門排隊買東西,假如你沒有帶手機,那你只能老老實實排隊等輪到你,在這之前,你除了排隊這件事之外,其他什麼事情都幹不了也不允許幹,這個時候你就是阻塞的,因為你只能做排隊這一件事。那麼同樣是排隊,這一次你帶了手機,那你排隊的時候,可以邊玩手機邊排隊,這個時候你就是非阻塞的。

接下來讓我們對應到上面的兩個階段,如果等待核心準備資料的時候,執行執行緒可以去做其他的事,那麼在第一階段就是非阻塞的,否則就是阻塞的。如果在資料從核心空間拷貝到使用者空間階段,執行執行緒可以去幹其他的事,那麼第二階段就是非阻塞的,否則就是阻塞的。

阻塞式IO(BIO)

阻塞式IO,是在兩個階段都阻塞的一種IO模型,使用者發起IO請求,在等待資料和資料拷貝階段,都會被阻塞,只有這兩個階段都完成了,才能去做下一階段的事情。

就像是你沒帶手機去吃飯,你跟老闆說要吃魚香肉絲,然後要等老闆做好菜(準備資料),然後從廚房把菜端到你面前(資料拷貝),這兩個階段你都只能等著,什麼事都幹不了。

由於BIO阻塞時間長,因此相對效能就會較低,所以現在用的相對也比較少了。

非阻塞IO(NIO)

非阻塞IO,可以看作是半阻塞IO,因為他在第一階段資料準備階段不阻塞,第二階段資料拷貝階段阻塞,當使用者發出IO請求的時候,會有一個執行緒去詢問核心資料準備好了嗎,一直問一直問,在這期間,使用者主程式可以去幹其他的事,等資料準備好了,到了第二階段,這個時候,使用者執行緒就要執行拷貝資料,這個時候是阻塞的。這種方式的缺點就是反覆的輪訓去詢問核心資料好了沒,是很消耗CPU資源的。

就像是你帶手機去吃飯,你點好菜之後,你可以一直問老闆,我的菜好了沒,老闆說沒有,問完之後就可以繼續玩手機繼續等,繼續問。等到有一次你問,老闆我的菜好了嗎,老闆說好了,你自己過來端一下。(注意,問菜好沒有的,得是你自己問,這家NIO店的老闆比較高冷,菜好了你不問他是不會主動告訴你的,這就是NIO的特點,資料準備就緒是使用者執行緒主動發出的詢問),這個時候菜好了,你要自己去端(資料拷貝),這個端菜的階段,你期間啥都幹不了,也不能玩手機,所以NIO的第二個階段是阻塞的。

說到這裡我們可以看出BIO和NIO之間的區別了,一個是傻等老闆做菜給你,期間你什麼都幹不了,一個是自己主動詢問老闆,菜好了沒,期間你可以玩手機,或者幹其他的,相比BIO,NIO的效率就高了很多。當然,你可能會問了,為啥菜好沒好,還得我自己主動去問,這也太不人性化了,確實,這個問題我們想得到,計算機的科學界大師們自然也想得到,於是為了解決這個問題,於是出現了訊號驅動IO和IO多路複用。

IO多路複用

通過我們上面對NIO的瞭解,我們可以知道,NIO多少存在著一些不夠好的地方,因為反覆的輪訓也是很消耗cpu資源的。如果飯店的人少還好說,但是如果飯店人多起來了,比如說來了幾百個人,那每個人時不時就要發起一次詢問請求,那老闆管不過來啊,cpu佔用率也會非常高。於是,IO多路複用就出現了,IO多路複用可以說是目前用的最多的一個IO模型,在不同的作業系統核心,也有不同的實現方式,在這篇文章中,我們IO多路複用的大概思想,至於詳細介紹,後面會用一篇文章來詳細的介紹IO多路複用

IO多路複用,實際上,是通過IO請求都通過一個selector來管理,使用者程式的IO請求就不直接發給核心處理程式了,而是註冊到這個selector上面,由selector來告訴核心需要哪些資料,然後定時的去查詢核心程式,我這個selector上需要的資料,有哪些準備好了,然後再由selector告訴那些準備好了的使用者執行緒,讓該使用者執行緒去拷貝資料。在非阻塞IO中,不斷地詢問狀態時通過使用者執行緒去進行的,而在IO多路複用中,詢問每個狀態是核心在進行的,在IO請求非常多的時候,這個效率要比使用者執行緒輪詢要高的多。

就像是你帶手機去飯店吃飯,現在這家飯店的老闆由於生意越來越好,人越來越多,他有點管理不過來了,於是他請了幾個服務員(selector)協助管理,然後現在飯店客戶的點餐都是告訴服務員,我需要什麼菜,然後服務員把xx桌客戶的菜,記在自己的單子上。然後服務員告訴廚房他這個單子上需要哪些菜,讓廚房去做。。服務員定時問廚房看看有哪些菜已經準備好了,然後告知15號桌和89號桌客人你們的菜已經好了,請來前臺端一下,然後你就去前臺端菜,端菜的階段是阻塞的。

來比較一下IO多路複用和NIO,我們可以發現,當IO請求多的時候,IO多路複用效率無疑是更高的。因為對於使用者執行緒來說,你點完菜就可以一直玩手機了,不用因為一直問老闆而分心分神,耽誤你打王者,因為菜好了,服務員會通知你

訊號驅動IO

通過我們上面兩種IO模型的瞭解,我們可以知道,不管是NIO還是IO多路複用,本質上還是輪詢,只不過NIO是使用者執行緒輪詢,IO多路複用是委託給selector讓他來輪詢,那有沒有什麼辦法能讓核心主動通知資料好了沒。所以,訊號驅動IO出現了。訊號訊號,顧名思義,就是會有一個訊號通知你資料已經準備好了,不用你一直去問。訊號驅動IO,使用者執行緒發出一個請求告訴核心我需要什麼資料,資料準備好了你告訴我一聲,然後核心就會記錄下這個請求,核心準備好了之後會主動通知使用者執行緒去執行拷貝資料,資料拷貝階段是阻塞的,需要等資料拷貝完才能做其他的事。

就像是你帶手機去吃飯,你點好菜之後,你就只管玩手機了,啥也不用管,就等老闆通知你,期間你想幹啥就幹啥,等到菜準備好了,老闆會大聲說(核心主動通知使用者程式),xxx你的魚香肉絲已經準備好了,請過來前臺端一下,這個時候你要自己去端(資料拷貝),這個端菜的階段,你期間啥都幹不了,也不能玩手機,所以訊號驅動IO的第二個階段也是阻塞的。

我們對比訊號驅動IO和NIO,可以發現最重要的區別就是NIO是使用者主動詢問核心資料好了嗎,而訊號驅動IO是核心主動通知使用者資料已經好了,這就改善了上面說的NIO的問題。

全非同步IO

全非同步IO是最理想的一種IO模型,所謂全非同步IO就是,使用者程式發起了一個IO請求,接下來可以幹其他的事了,不需要等核心準備好,也不需要執行資料拷貝,資料非同步拷貝到使用者空間之後,使用者程式直接拿來用就行了,這兩個階段都是由核心自動完成。完全不用使用者執行緒操心這些事。

前面四種IO模型實際上都屬於同步IO,只有最後一種才是是真正的非同步IO,因為不管是是IO多路複用還是訊號驅動,IO操作的第2個階段都會讓使用者執行緒阻塞,也就是核心進行資料拷貝的過程都會讓使用者執行緒阻塞。

舉個例子就像是,你去飯店吃飯,點好餐之後,你就可以玩手機了,飯菜做好之後,服務員會把飯菜端到你的面前,你也不需要自己去端,你需要點餐和吃飯就行了,其他的你都不用管。簡單來說,就是發出請求之後,只需要等待資料完成直接使用,等待期間,你可以做其他的事。整個過程完全的非同步,體驗最好。

全非同步IO雖然非常牛逼,但是現在還不是很成熟,支援全非同步IO的作業系統和框架也還不是很多,所以用的也不是很多。我們只需要瞭解一下就行了

總結

我們這篇文章講了五種IO模型的思想,並且每種模型我們都通過一個通俗易懂的例子,來描繪其過程。相信你看完之後一定有收穫。其中比較重要的兩種是NIO和IO多路複用,這是目前來說用的最多的兩種,後面的篇幅,會專門的講這兩種模型,尤其是IO多路複用,在不同的OS上,又有select,poll,和epoll方式。等下一篇文章,我們將會細講。

相關文章