I/O Model
阻塞 I/O (Blocking I/O)
應用程式發出 I/O 請求後,會等待核心完成操作並返回結果,期間無法執行其他任務。
實例
- C: read() 函式讀取檔案或網路資料。
- Java: 使用 InputStream.read() 或 Socket.accept() 就是阻塞的。
非阻塞 I/O (Non-blocking I/O)
應用程式發出 I/O 請求後立即返回,若數據未就緒則返回錯誤。應用程式需反覆輪詢,直到數據準備好再讀取。
實例
- C: 將 Socket 設定為 O_NONBLOCK 後,呼叫 read() 函式。若無數據可讀,會立即返回 EWOULDBLOCK 或 EAGAIN 錯誤。
- Java NIO: SocketChannel.configureBlocking(false) 讓通道進入非阻塞模式。
I/O 多工 (I/O Multiplexing)
單一執行緒可同時監聽多個文件描述符的 I/O 就緒事件。它會阻塞直到有 FD(File descriptor) 就緒,然後再對就緒的 FD 執行非阻塞 I/O。
實例
- C (Linux): 高併發 Web 伺服器使用 epoll 監聽數千個客戶端連接,當某個連接有數據可讀時才去處理它。
- Java NIO: Selector 用於同時監聽多個 Channel 的事件(如新的連接、可讀數據),然後處理就緒的通道。
訊號驅動 I/O (Signal-driven I/O)
當 I/O 數據準備好時,核心會發送一個 SIGIO 訊號給應用程式,應用程式在訊號處理器中執行讀取操作。
實例
- C: 較少用於高併發,主要用於特定場景,例如監控終端設備。透過 fcntl() 設定 O_ASYNC 標誌。
非同步 I/O (Asynchronous I/O, AIO)
應用程式發出 I/O 請求後立即返回,不阻塞。核心在整個 I/O 操作完成後(包含數據複製)才通知應用程式。
實例
- C(Linux): 使用 io_uring 執行檔案讀寫。程式提交讀取請求後立即返回,當數據讀取並寫入應用程式指定緩衝區後,核心才發送完成事件。
- Java: java.nio.channels.AsynchronousFileChannel 和 AsynchronousSocketChannel 提供了此功能,底層依賴於作業系統的 AIO 實現。
優缺比較
I/O 模型 | 優點 | 缺點 | 適用場景 | 不適用場景 |
阻塞 I/O | 程式設計最簡單直觀 | 效率最低:I/O 等待期間應用程式/執行緒被完全阻塞,CPU 閒置。 | 簡單、低併發的應用,如一次性讀取小文件、命令列工具、單使用者應用程式。 | 高併發服務(如 Web 伺服器、聊天伺服器)、需要高吞吐量的資料庫操作。 |
非阻塞 I/O | 應用程式不被阻塞:I/O 請求立即返回,可以同時執行其他任務。 | CPU 消耗高:需要不斷輪詢 (Polling) 核心,檢查 I/O 狀態,造成忙等 (busy-waiting)。 | 不建議單獨使用,通常與 I/O 多工結合,用於設置文件描述符的非阻塞屬性。 | 任何需要高效處理 I/O 的場景,因為單獨使用效率極低。 |
I/O 多工 | 高效處理高併發:單一執行緒可同時管理大量 I/O 連接。 CPU 利用率高:避免了阻塞 I/O 的閒置和非阻塞 I/O 的輪詢開銷。 | 程式設計相對複雜:需要事件循環和狀態機管理。 數據仍需從核心複製到使用者空間。 | 高併發網路伺服器 (Nginx, Redis)、聊天室、代理伺服器等。 幾乎所有高併發的網路應用。 | 對極致低延遲或需要大量直接 I/O 的場景(如高性能資料庫底層)可能不夠。 |
訊號驅動 I/O | 非阻塞:I/O 等待期間應用程式不被阻塞。 異步通知:數據就緒時透過訊號通知應用程式。 | 程式設計複雜度高:訊號處理機制本身較難控制。 難以擴展:不適用於大量文件描述符。 | 某些特定場景,如監控少數幾個非同步事件,例如監聽串列埠數據的到達。 | 通用高併發網路應用、需要處理大量文件或套接字。 |
非同步 I/O (AIO) | 真正非阻塞:應用程式發出請求後,無需等待核心執行任何操作(包括數據複製)。 最高效:CPU 利用率最大化,I/O 與計算可完全重疊。 | 程式設計最複雜:需要處理完成事件的回呼或佇列,可能涉及記憶體管理。 平台相關:實作依賴作業系統原生支援。 | 高性能資料庫、大規模資料處理系統、檔案伺服器、需要極致 I/O 吞吐量和低延遲的應用。 | 簡單應用程式或對 I/O 效率要求不高的場景,因為複雜度太高。 |