I/O 多路複用使用背景梳理

內容總結自 《UNIX 環境高級編程》 高級 I/O 部分

1. 問題背景描述

在終端使用 telnet 命令連接到一個 TCP server,假設 server 會返回你在終端輸入的內容。

那麼現在終端的這個 telnet 進程,會從兩個文件描述符讀取內容,一個是標準輸入,一個是網絡連接(server 返回的數據),注意只有一個進程。

打開一個文件時我們可以以阻塞或非阻塞方式打開,假設此時我們使用阻塞的方式打開一個文件,並使用 read 來讀取數據,再假設此時 read 阻塞在標準輸入的文件描述符上,且標準輸入無數據,而網絡連接有數據達到時,由於程序此時阻塞在標準輸入,因此無法處理到達的網絡數據。

如果需要輸入源有多個,並且是阻塞的,那麼先考慮以多進程(父子進程)的方式實現;每個進程處理一個輸入源;雖然看似解決了阻塞的問題,但引出了結束進程的複雜性,比如父進程結束時通知子進程,子進程結束時需要通知父進程。

再嘗試以多線程的方式實現;數據從標準輸入讀取再從網絡連接發送出去,不可避免存在數據競態問題,因此需要做線程同步,又引入了較高的複雜性。

再考慮使用非阻塞的方式實現,並且僅用一個進程;那麼當標準輸入無數據時,read 會立即返回,因此可以繼續判斷網絡連接是否有數據,如果此時網絡連接也沒數據,可以立即判斷標準輸入,但此時會耗費極高的 CPU,因此在所有文件描述符都沒有數據的情況下,可以讓進程睡眠一段時間進而減少 CPU 消耗,但合適的睡眠時間很難確定。

以上的實現方案都基於同步方式,同步阻塞,同步非阻塞,再來看看異步的方式,即異步 I/O 技術。進程告訴內核(fnctl、ioctl):當描述符準備好 I/O 時,通過信號來通知進程。

但異步 I/O 的使用也有困難,比如不同系統提供的異步 I/O API 不一致,即可移植性差;異步 I/O 在使用上受限,比如 System V 提供 SIGPOLL 來支持異步 I/O,但只有描述符引用 STREAMS 設備(什麼是 STREAMS 設備,有哪些?)時纔可用,BSD 提供了 SIGIO 信號,但只有描述符引用終端設備或網絡時纔可用;另外,一個進程只能使用一個信號來處理異步 I/O,如果多個描述符共用一個信號,那麼信號發生時進程無法判斷是哪一個描述符準備好了。

由於上述方案的種種缺點,引出了 I/O 多路複用技術(I/O multiplexing)。複用是指在同一個進程(線程)中,處理多路 I/O,多路指多個文件描述符。它的思想是,收集進程感興趣的全部描述符,然後調用一個函數,當這些描述符中的一個或多個準備好 I/O 時,函數返回並告知進程是哪些描述符準備好了。此時進程只需要去這些準備好的描述符上操作即可。

2、常用的 I/O 多路複用模型

2.1 select

2.2 poll

2.3 epoll

只能在 Linux 平臺使用

  • 爲什麼性能優越?與 select 的區別是什麼?
    1、減少了不必要的用戶態到內核態的數據拷貝
    2、不用輪詢所監聽的套接字,減少了無謂的 CPU 消耗

  • 水平觸發模式(edge-triggered)
    讀緩衝區從空到非空,會一直觸發可讀事件,直到又變爲非空。
    寫緩衝區從滿到非滿,會一直觸發可寫事件,直到又變爲滿。

  • 邊緣觸發模式(level-triggered)
    讀緩衝區從空到非空,或者寫緩衝區從滿變爲非滿時會且僅會觸發一次事件。

  • 哪些開源項目使用 epoll
    nginx,libevent,redis 等

  • 使用 epoll 的限制有什麼?
    只能在單進程(線程)中使用,必須使用非阻塞 I/O。

2.4 kqueue

只能在 BSD 平臺使用,例如 MacOS

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章