聊聊五種 I/O 模型
什麼是 I/O
I/O 是 Input/Output 的簡寫,即輸入/輸出,是計算機與外部裝置(鍵盤、滑鼠、磁碟等)通訊的統稱,與具體實現無關。
與外部裝置的通訊其實就是對外部裝置進行讀取或寫入資料的過程,比如對檔案的讀寫操作可以稱為檔案 I/O、對套接字的讀寫操作稱為網路 I/O。
轉載請註明來源地址:她和她的貓
同步 I/O 和非同步 I/O
同步和非同步是相對於獲取資料的過程而言的。
- 同步 I/O:包含阻塞 I/O、非阻塞 I/O、I/O 多路複用、訊號驅動式I/O 四種 I/O 模型,它們的共同點就是在執行 I/O 操作時會阻塞程序,直到 I/O 操作完成。
- 非同步 I/O:執行 I/O 操作時不會阻塞程序。
在使用者程序中執行 read 系統呼叫時,核心將資料從核心空間拷貝使用者程序空間。如果沒有讀到資料,那麼程序會一直處於阻塞狀態,當讀取到資料時才會恢復程序,繼續執行後面的邏輯,所以我們稱這個操作是同步的。
非同步只需要執行 aio_read 系統呼叫告訴核心從哪兒讀取資料就可以了,程序不用等待立即返回,核心會非同步地將資料從核心空間拷貝到使用者程序空間。
阻塞 I/O 模型
套接字在建立時預設就是阻塞的。
使用者程序執行 read 系統呼叫,如果資料沒有準備好,那麼程序將會被掛起,直到資料準備好之後核心將資料拷貝給使用者程序。
舉一個燒開水的例子,水壺是套接字,水壺裡的水是資料,水未燒開說明資料沒有準備好。
現在有 A、B、C 三個水壺,程序查詢 A 的狀態,發現水還沒有燒開就一直等著水燒開,哪怕 B、C 的水燒開了也不管,直到 A 的水燒開了才會去處理下一個水壺。
缺點:每次只能對一個套接字進行操作,就算其它套接字資料準備好了也沒辦法立即處理。
非阻塞 I/O 模型
在 PHP 中可以呼叫 socket_set_nonblock 函式將套接字設定為非阻塞的。
非阻塞 I/O 在資料未準備好的情況下,執行 read 系統呼叫將會立即返回,應用程式可以使用迴圈不停地輪訓核心,直到資料準備好,核心將資料從拷貝到應用程式中。
虛擬碼:
while (true) {
rbytes = read(fd);
if (rbytes < 0 && errno == EWOULDBLOCK) {
continue;
}
// 處理資料
}
用燒開水的例子來解釋就是,程序不斷地輪詢每個水壺的狀態,當某個水壺的水燒開了之後就執行下一步操作。
缺點:需要不停地輪詢核心,浪費系統資源。
I/O 多路複用模型
多路複用就是多個網路請求複用同一個程序,讓單程序的應用程式擁有了同時處理多個套接字的能力,避免不停地輪詢核心,造成資源浪費。
將需要監聽的套接字交給核心,然後程序被掛起陷入休眠狀態,當套接字資料準備好時,核心將對應的套接字及事件返回給程序並喚醒程序,程序就可以執行 read 系統呼叫讀取資料,這個時候資料肯定是準備好的。
目前常見的有三種多路複用機制,分別是 select、poll、epoll。
多路複用機制 | 平臺支援 | 底層實現 | 時間複雜度 | 最大連線數 | fd 拷貝 |
---|---|---|---|---|---|
select | Linux/Windows | 陣列 | O(n) | 1024 | 每次呼叫 select 都需要從使用者程序拷貝到核心 |
poll | Linux | 連結串列 | O(n) | 無上限 | 每次呼叫 poll 都需要從使用者程序拷貝到核心 |
epoll | Linux | 紅黑樹 | O(1) | 無上限 | 呼叫 epoll_ctl 時需要從使用者程序拷貝到核心,epoll_wait 不需要 |
從上表可以看出 epoll 效能最好,所以當程式執行在 Linux 系統上應該使用 epoll,如果是 Windows 可以使用 select,這樣我們就可以實現跨平臺的多路複用應用程式。
燒開水的例子:將 A、B、C 三個水壺交給核心,當有水燒開時核心就通知程序哪個水壺燒開了。
I/O 多路複用中的套接字必須設定為非阻塞的。
訊號驅動式 I/O 模型
呼叫 sigaction 安裝 SIGIO 訊號處理器,為套接字設定宿主程序,當套接字的資料準備好時,作業系統會觸發 SIGIO I/O 就緒訊號,就會執行安裝的訊號處理器,在訊號處理器中執行 I/O 操作。
程序會安裝 SIGIO 訊號的處理函式,讓核心有資料準備好時就觸發 SIGIO 訊號,並執行該訊號對應的處理函式。
用水壺的例子來解釋就是給每個水壺都安裝了一個蜂鳴器,當水燒開時就開始響,程序就知道哪個水壺燒開了。
非同步 I/O 模型
前面這四種 I/O 模型都是同步 I/O,不管是程序是如何知道資料是否準備好的,最終執行 read 系統呼叫從核心拷貝資料到使用者程序的過程是同步的,而非同步 I/O 的區別就在於這裡。
非同步 I/O 的讀寫操作都是立即返回的,讀寫操作由核心非同步地執行,資料拷貝的過程不會阻塞使用者程序。
相當於告訴核心,水燒開了就倒在這個杯子裡,我想喝水了就自己去喝。
總結
同步 I/O 和非同步 I/O 的區別:核心將資料從核心空間拷貝到使用者程序空間時,是否會阻塞使用者程序。
阻塞 I/O 和非阻塞 I/O 的區別:資料沒有準備好的時候是否會阻塞使用者程序。