[翻譯]各個類型的IO - 阻塞, 非阻塞,多路復用和異步


同事推薦,感覺寫的不錯就試着翻譯了下.
原文鏈接: https://www.rubberducking.com/2018/05/the-various-kinds-of-io-blocking-non.html
作者: Didier A.

我發現對於軟件程序員來說很難分清楚各種類型的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

Multiplexed I/O

Reactor pattern

Proactor pattern

There is no thread

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

I/O Completion Ports

免責聲明

我不是一個系統層面的程序員,我也不是一個操作系統提供的所有種類IO方面的專家.這篇文章是我盡可能總結我所知的內容,更偏向於中間層面的知識.所以如果你發現有任何問題的話請指正我.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM