各個型別的IO - 阻塞, 非阻塞,多路複用和非同步
同事推薦,感覺寫的不錯就試著翻譯了下.
原文連結:https://www.rubberducking.com/2018/05/the-various-kinds-of-io-blocking-non.html
我發現對於軟體程式設計師來說很難分清楚各種型別的IO.對於阻塞,非阻塞,多路複用和非同步IO有很多的混淆點.所以我想嘗試解釋清楚各種IO型別意味著什麼
在硬體層面.
在現代作業系統中,IO(輸入/輸出)是一種和外圍裝置交換資料的方式.包括讀寫磁碟或SSD,通過網路傳送和接受資料,在顯示器上顯示,接入鍵盤和滑鼠輸入,等等.
現代作業系統和外圍裝置的交流取決於外圍裝置的特定型別以及他們的韌體版本和硬體能力.通常來說,你可以認為外圍裝置是很高階的,他們可以同時處理多個併發的讀寫資料請求.也就是說,序列交流的日子一去不返了.在這些場景中,外圍裝置和CPU間的交流在硬體層面都是非同步的.
這個非同步機制被稱為硬體中斷.想想一個簡單的場景,CPU請求外圍裝置去讀取一些資料,接著CPU會進入一個無限迴圈,每一次都會檢查外圍裝置的資料是否可用,直到獲得了資料為止.這種方法被稱為拉取(polling),因為CPU需要保持檢查外圍裝置.在現代硬體中,取而代之發生的是CPU請求外圍硬體執行一個操作,然後就忘了這件事,繼續處理其他的CPU指令.只要外圍裝置做完了,他會通過電路中斷來通知CPU.這發生在硬體中,CPU因此不需要等下來或者檢查這個外圍裝置,繼續執行其他的工作,直到周邊裝置說已經做完了.
在軟體層面
現在我們瞭解了硬體中發生的事,我們可以移動到軟體這一側了.在這一層IO通過多種方式被暴露,阻塞,非阻塞,多路複用和非同步.讓我們一個個來仔細解釋.
阻塞
還記得使用者程式如何在一個程序內執行,程式碼是線上程的上下文中執行的嗎?你總是會遇到一種情況編寫一個需要從檔案中讀取資料的程式.使用阻塞IO,你所做的是從你的執行緒中請求作業系統,將執行緒置於休眠(sleep),當資料可用於被消費時喚醒執行緒.
也就是說,阻塞IO之所以被稱為阻塞是因為使用他的執行緒會被阻塞直到IO完成.
非阻塞
阻塞IO的問題是當你的執行緒在休眠時,他除了等IO完成不能幹其他事.有時候,你的程式可能沒有其他事可做了.但還有其他事需要做的時候,如果能在等待IO的時候併發做可是極好的.
其中一種實現方式被稱為非阻塞IO.他的思想是當你讀取一個檔案時,OS只是簡單返回給你檔案的內容或者一個等待狀態告訴你IO還未完成,而不是將執行緒休眠.他不會阻塞你的執行緒,但之後檢查IO是否完成的工作還是交給了你.這意味著當處於等待狀態時,你可以去做一些工作,當你再次需要IO時,可以再讀取一次,那時候IO可能已經完成了,檔案的內容會返回,如果還是處於等待狀態的話,你可以選擇繼續做其他事.
多路複用
非阻塞IO的問題是如果你在等待IO的過程中要做的其他事情就是另外的IO的話,事情會變得很奇怪.
在一個好的場景下,你請求OS去讀取檔案A的內容,然後去做一些重計算的工作,做完之後再去檢查檔案A是否完成讀取,如果完成了,你再做一些關於這個檔案內容的操作,不然就繼續做其他的工作,迴圈往復.但一個壞的場景,你沒有重計算的工作要去做,而是需要讀取另一個檔案B.那除了等待他們還有什麼事要做呢?沒有了,你的程式就進入了一個死迴圈,判斷檔案A是否被讀取完畢,接著再去判斷檔案B,一遍又一遍.要麼你使用簡單的狀態輪詢消耗CPU,或者你手動加入一些隨意的休眠時間,不過這也意味著你將延遲知道IO完成,降低程式的吞吐.
為了避免這個問題,你可以使用多路複用IO來代替.他所做的是你再次阻塞在IO上,但不僅僅是一個一個的IO操作,你可以將所有需要的IO操作入隊,阻塞在他們所有上.當其中有一個IO完成之後OS會喚醒你.一些多路複用的實現提供了更多的控制,你可以設定在特定一些IO操作完成之後再被喚醒,例如A和C檔案或B和D檔案完成的時候.
所有你可以呼叫非阻塞讀取檔案A,然後非阻塞讀取檔案B,最後告訴作業系統,將我的執行緒置於休眠,當A和B的IO都完成的時候或其中一個完成的時候再喚醒他.
非同步
多路複用IO的問題是在IO完畢前你還是處於休眠狀態.又一次,這對一些程式來說可行,那些除了等待IO操作完成外沒有其他操作要去執行的.但有時候,你確實需要去做其他事情.可能你正在計算PI的數字,同時也在彙總一群檔案的值.你想要進行的操作是將所有的讀操作入佇列,當等待他們讀取完成前,你可以繼續計算PI.當一個檔案讀取完成後,你可以彙總他的值,然後繼續進行PI的計算直到另一個檔案完成讀取.
為了讓這可行,你需要一種方式當IO完成時中斷PI的計算,並且你需要IO來執行這個操作當他完成時.
這通過事件回撥完成.執行讀操作的呼叫會需要一個回撥,並且呼叫立即返回.當IO完成時,作業系統會掛起你的執行緒,並執行你的回撥.當回撥完成時,他會恢復你的執行緒.
多執行緒 vs 單執行緒?
你可能已經注意到我所描述的所有執行緒都是關於單個執行緒的,也就是你的主執行緒.真相是,IO不需要被一個執行緒來執行,因為我在最開始已經解釋過了,外圍裝置都是在他們自己的電路里非同步執行IO.所以阻塞,非阻塞,多路複用和非同步IO都是可能在單執行緒模型中被執行的.這也是為什麼併發IO可以不借助於多執行緒支援來工作.
現在,對於處理IO操作完成的結果,或者請求IO操作很明顯是可以多執行緒的,如果你需要的話.這允許你在併發IO之上執行併發計算.所以沒有什麼東西阻止多執行緒和這些IO機制結合.
事實上,這裡也有第五種很受歡迎的基於多執行緒的IO.他經常被混淆為非阻塞IO或非同步IO,因為他對外暴露出的是類似他們的介面.真相是,他是假裝的非阻塞或非同步IO.他的工作方式很簡單,他使用阻塞IO,但是每個阻塞操作都是在他自己的執行緒中(注:多執行緒環境,非主執行緒中).現在取決於他的實現機制,他要麼接收一個回撥,或者使用一種拉取模型,比如返回一個Future物件.
最後
我希望這篇文章可以幫助你澄清對多種IO的理解.還有很重要的一點需要注意,他們不是被所有的作業系統和所有的外圍裝置支援的.相似的,不是所有的程式語言都暴露了作業系統支援的所有IO型別的API.
這邊請,所有型別的IO都解釋了.
希望能對你有所幫助.
更多閱讀
non-blocking IO vs async IO and implementation in Java
Asynchronous and non-blocking IO
Asynchronous I/O and event notification on linux
What is the status of POSIX asynchronous I/O (AIO)?
Kernel Asynchronous I/O (AIO) Support for Linux
免責宣告
我不是一個系統層面的程式設計師,我也不是一個作業系統提供的所有種類IO方面的專家.這篇文章是我儘可能總結我所知的內容,更偏向於中間層面的知識.所以如果你發現有任何問題的話請指正我.