Skip to main content

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 效率要求不高的場景,因為複雜度太高。