首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

Linux 並發 I/O 三大方案深度對比:非阻塞 I/O、I/O 多路復用、異步 I/O

     編輯:關於C語言

並發 I/O 模型分類

Linux 網路/系統程式設計中,處理多路 I/O 並發的三大主流方案:

  1. 多路非阻塞 I/O:輪詢 + O_NONBLOCK
  2. I/O 多路復用:select/poll/epoll 內核級事件通知
  3. 異步 I/O (AIO):內核完成整個 I/O 操作後通知使用者(aio_read/io_uring)

本文以「同時讀取鍵盤(stdin)與滑鼠(/dev/input/mouse1)」為例,展示前兩種方案的完整代碼實現。(註:Linux 原生 AIO 支援有限,範例採用 SIGIO 信號模擬)

方案一:多路非阻塞 I/O(輪詢模式)

核心:對所有關注的檔案描述符設定 O_NONBLOCK,迴圈中嘗試 read,EAGAIN 表示無資料,繼續下一個。

#include 
#include 
#include 
#include 

#define MOUSE_PATH "/dev/input/mouse1"

int main() {
    int fd = open(MOUSE_PATH, O_RDONLY | O_NONBLOCK);
    if (fd < 0) { perror("open mouse"); return 1; }

    // 設定 stdin 為非阻塞
    int flags = fcntl(0, F_GETFL);
    fcntl(0, F_SETFL, flags | O_NONBLOCK);

    char buf[200];
    while (1) {
        // 讀滑鼠
        memset(buf, 0, sizeof(buf));
        int ret = read(fd, buf, 50);
        if (ret > 0) printf("滑鼠: [%s]\n", buf);

        // 讀鍵盤
        memset(buf, 0, sizeof(buf));
        ret = read(0, buf, 50);
        if (ret > 0) printf("鍵盤: [%s]\n", buf);

        // 實際專案需加 usleep 降低 CPU 佔用
    }
    return 0;
}

缺點:忙等待(Busy Waiting)浪費 CPU;若描述符眾多,輪詢延遲大。

方案二:I/O 多路復用 — select 實現

select 讓內核監視多個 fd,任一就緒即返回,避免使用者態輪詢。

#include 
#include 

int main() {
    int fd = open(MOUSE_PATH, O_RDONLY);
    fd_set readset;
    struct timeval tv = {10, 0};  // 10 秒超時

    while (1) {
        FD_ZERO(&readset);
        FD_SET(fd, &readset);
        FD_SET(0, &readset);

        int ret = select(fd + 1, &readset, NULL, NULL, &tv);
        if (ret < 0) { perror("select"); break; }
        if (ret == 0) { puts("Timeout"); break; }

        if (FD_ISSET(fd, &readset)) {
            char buf[300] = {0};
            read(fd, buf, 50);
            printf("滑鼠: [%s]\n", buf);
        }
        if (FD_ISSET(0, &readset)) {
            char buf[300] = {0};
            read(0, buf, 50);
            printf("鍵盤: [%s]\n", buf);
        }
    }
    return 0;
}

方案二補充:poll 實現

poll 使用 struct pollfd 陣列,無 fd 數量限制(select 受 FD_SETSIZE 限制,通常 1024)。

#include 

struct pollfd fds[2] = {
    {0, POLLIN, 0},      // stdin
    {fd, POLLIN, 0}      // mouse
};

int ret = poll(fds, 2, 10000);  // 10 秒超時(毫秒)
if (ret > 0) {
    for (int i = 0; i < 2; i++) {
        if (fds[i].revents & POLLIN) {
            char buf[1024] = {0};
            read(fds[i].fd, buf, sizeof(buf));
            printf("fd %d: [%s]\n", fds[i].fd, buf);
        }
    }
}

方案三:異步 I/O — 信號驅動模擬(SIGIO)

真正的 AIO (aio_read/io_uring) 需核心支援與連結 -laio。此處示範傳統 SIGIO 機制:檔案就緒時內核發送 SIGIO,使用者註冊處理函式。

#include 
#include 

int mousefd;
void sigio_handler(int sig) {
    if (sig != SIGIO) return;
    char buf[1024] = {0};
    read(mousefd, buf, sizeof(buf));
    printf("滑鼠(異步): [%s]\n", buf);
}

int main() {
    mousefd = open(MOUSE_PATH, O_RDONLY);
    // 1. 設定檔案支援異步通知
    int flags = fcntl(mousefd, F_GETFL);
    fcntl(mousefd, F_SETFL, flags | O_ASYNC);
    // 2. 設定接收 SIGIO 的進程/線程
    fcntl(mousefd, F_SETOWN, getpid());
    // 3. 註冊信號處理函式
    signal(SIGIO, sigio_handler);

    // 主迴圈處理鍵盤(同步阻塞)
    char buf[1024];
    while (1) {
        memset(buf, 0, sizeof(buf));
        read(0, buf, sizeof(buf));
        printf("鍵盤: [%s]\n", buf);
    }
}

三大方案核心對比表

維度非阻塞輪詢select/pollepoll (LT/ET)異步 I/O (AIO/io_uring)
程式複雜度低中中高高
CPU 佔用高(忙等待)低(阻塞於內核)極低極低
最大連接數受限於輪詢頻率1024 (select) / 無限 (poll)百萬級百萬級
適用場景極少量 fd、簡單工具中小型服務、跨平台高併發伺服器 (Nginx, Redis)高效能儲存/網路、資料庫引擎
資料拷貝用戶態多次用戶態多次可零拷貝 (splice)內核直接到用戶緩衝區

現代最佳實踐建議

  • Linux 專用高併發:首選 epoll (LT 模式) 或 io_uring (Linux 5.1+,真正的非阻塞提交/完成)。
  • 跨平台庫:使用 libuv (Node.js 底層)、libevent、libev、ASIO (C++) 封裝平台差異。
  • 避免:手寫 select/poll 處理大量連線;在高併發場景使用 SIGIO(信號處理複雜、不可重入風險高)。

總結

從「使用者態輪詢」到「內核態事件通知」再到「內核完成 I/O 後回呼」,並發 I/O 模型演進的核心是**將等待與調度下沉至內核**,釋放 CPU 執行業務邏輯。理解三大模型的原理、優劣與適用邊界,是系統程式設計師的必修課。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved