聊聊IO多路複用之select、poll、epoll詳解

#0 系列目錄#編程

IO多路複用是指內核一旦發現進程指定的一個或者多個IO條件準備讀取,它就通知該進程。IO多路複用適用以下場合:數組

  1. 當客戶處理多個描述符時(通常是交互式輸入和網絡套接口),必須使用I/O複用。服務器

  2. 當一個客戶同時處理多個套接口時,而這種狀況是可能的,但不多出現。網絡

  3. 若是一個TCP服務器既要處理監聽套接口,又要處理已鏈接套接口,通常也要用到I/O複用。數據結構

  4. 若是一個服務器即要處理TCP,又要處理UDP,通常要使用I/O複用。多線程

  5. 若是一個服務器要處理多個服務或多個協議,通常要使用I/O複用。併發

與多進程和多線程技術相比,I/O多路複用技術的最大優點是系統開銷小,系統沒必要建立進程/線程,也沒必要維護這些進程/線程,從而大大減少了系統的開銷。框架

目前支持I/O多路複用的系統調用有 select,pselect,poll,epoll,I/O多路複用就是經過一種機制,一個進程能夠監視多個描述符,一旦某個描述符就緒(通常是讀就緒或者寫就緒),可以通知程序進行相應的讀寫操做但select,pselect,poll,epoll本質上都是同步I/O,由於他們都須要在讀寫事件就緒後本身負責進行讀寫,也就是說這個讀寫過程是阻塞的,而異步I/O則無需本身負責進行讀寫,異步I/O的實現會負責把數據從內核拷貝到用戶空間。異步

對於IO多路複用機制不理解的同窗,能夠先行參考《聊聊Linux 五種IO模型》,來了解Linux五種IO模型。socket

#1 select、poll、epoll簡介# epoll跟select都能提供多路I/O複用的解決方案。在如今的Linux內核裏有都可以支持,其中epoll是Linux所特有,而select則應該是POSIX所規定,通常操做系統均有實現。

##1.1 select## 基本原理:

select 函數監視的文件描述符分3類,分別是writefds、readfds、和exceptfds。調用後select函數會阻塞,直到有描述符就緒(有數據 可讀、可寫、或者有except),或者超時(timeout指定等待時間,若是當即返回設爲null便可),函數返回。當select函數返回後,能夠經過遍歷fdset,來找到就緒的描述符。

基本流程,如圖所示:

輸入圖片說明

select目前幾乎在全部的平臺上支持,其良好跨平臺支持也是它的一個優勢select的一個缺點在於單個進程可以監視的文件描述符的數量存在最大限制,在Linux上通常爲1024,能夠經過修改宏定義甚至從新編譯內核的方式提高這一限制,可是這樣也會形成效率的下降。

select本質上是經過設置或者檢查存放fd標誌位的數據結構來進行下一步處理。這樣所帶來的缺點是:

  1. select最大的缺陷就是單個進程所打開的FD是有必定限制的,它由FD_SETSIZE設置,默認值是1024。

通常來講這個數目和系統內存關係很大,具體數目能夠cat /proc/sys/fs/file-max察看。32位機默認是1024個。64位機默認是2048.

  1. 對socket進行掃描時是線性掃描,即採用輪詢的方法,效率較低。

當套接字比較多的時候,每次select()都要經過遍歷FD_SETSIZE個Socket來完成調度,無論哪一個Socket是活躍的,都遍歷一遍。這會浪費不少CPU時間。若是能給套接字註冊某個回調函數,當他們活躍時,自動完成相關操做,那就避免了輪詢,這正是epoll與kqueue作的。

  1. 須要維護一個用來存放大量fd的數據結構,這樣會使得用戶空間和內核空間在傳遞該結構時複製開銷大。

##1.2 poll## 基本原理:

poll本質上和select沒有區別,它將用戶傳入的數組拷貝到內核空間,而後查詢每一個fd對應的設備狀態,若是設備就緒則在設備等待隊列中加入一項並繼續遍歷,若是遍歷完全部fd後沒有發現就緒設備,則掛起當前進程,直到設備就緒或者主動超時,被喚醒後它又要再次遍歷fd。這個過程經歷了屢次無謂的遍歷。

它沒有最大鏈接數的限制,緣由是它是基於鏈表來存儲的,可是一樣有一個缺點:

  1. 大量的fd的數組被總體複製於用戶態和內核地址空間之間,而無論這樣的複製是否是有意義。

  2. poll還有一個特色是「水平觸發」,若是報告了fd後,沒有被處理,那麼下次poll時會再次報告該fd。

注意:

從上面看,select和poll都須要在返回後,經過遍歷文件描述符來獲取已經就緒的socket。事實上,同時鏈接的大量客戶端在一時刻可能只有不多的處於就緒狀態,所以隨着監視的描述符數量的增加,其效率也會線性降低。

##1.3 epoll## epoll是在2.6內核中提出的,是以前的select和poll的加強版本。相對於select和poll來講,epoll更加靈活,沒有描述符限制。epoll使用一個文件描述符管理多個描述符,將用戶關係的文件描述符的事件存放到內核的一個事件表中,這樣在用戶空間和內核空間的copy只需一次

基本原理:

epoll支持水平觸發和邊緣觸發,最大的特色在於邊緣觸發,它只告訴進程哪些fd剛剛變爲就緒態,而且只會通知一次。還有一個特色是,epoll使用「事件」的就緒通知方式,經過epoll_ctl註冊fd,一旦該fd就緒,內核就會採用相似callback的回調機制來激活該fd,epoll_wait即可以收到通知。

epoll的優勢:

  1. 沒有最大併發鏈接的限制,能打開的FD的上限遠大於1024(1G的內存上能監聽約10萬個端口)。

  2. 效率提高,不是輪詢的方式,不會隨着FD數目的增長效率降低。只有活躍可用的FD纔會調用callback函數;即Epoll最大的優勢就在於它只管你「活躍」的鏈接,而跟鏈接總數無關,所以在實際的網絡環境中,Epoll的效率就會遠遠高於select和poll。

  3. 內存拷貝,利用mmap()文件映射內存加速與內核空間的消息傳遞;即epoll使用mmap減小複製開銷

epoll對文件描述符的操做有兩種模式:LT(level trigger)和ET(edge trigger)。LT模式是默認模式,LT模式與ET模式的區別以下:

LT模式:當epoll_wait檢測到描述符事件發生並將此事件通知應用程序,應用程序能夠不當即處理該事件。下次調用epoll_wait時,會再次響應應用程序並通知此事件。

ET模式:當epoll_wait檢測到描述符事件發生並將此事件通知應用程序,應用程序必須當即處理該事件。若是不處理,下次調用epoll_wait時,不會再次響應應用程序並通知此事件。

  1. LT模式

LT(level triggered)是缺省的工做方式,而且同時支持block和no-block socket。在這種作法中,內核告訴你一個文件描述符是否就緒了,而後你能夠對這個就緒的fd進行IO操做。若是你不做任何操做,內核仍是會繼續通知你的

  1. ET模式

ET(edge-triggered)是高速工做方式,只支持no-block socket。在這種模式下,當描述符從未就緒變爲就緒時,內核經過epoll告訴你。而後它會假設你知道文件描述符已經就緒,而且不會再爲那個文件描述符發送更多的就緒通知,直到你作了某些操做致使那個文件描述符再也不爲就緒狀態了(好比,你在發送,接收或者接收請求,或者發送接收的數據少於必定量時致使了一個EWOULDBLOCK 錯誤)。可是請注意,若是一直不對這個fd做IO操做(從而致使它再次變成未就緒),內核不會發送更多的通知(only once)

ET模式在很大程度上減小了epoll事件被重複觸發的次數,所以效率要比LT模式高。epoll工做在ET模式的時候,必須使用非阻塞套接口,以免因爲一個文件句柄的阻塞讀/阻塞寫操做把處理多個文件描述符的任務餓死。

  1. 在select/poll中,進程只有在調用必定的方法後,內核纔對全部監視的文件描述符進行掃描,而epoll事先經過epoll_ctl()來註冊一個文件描述符,一旦基於某個文件描述符就緒時,內核會採用相似callback的回調機制,迅速激活這個文件描述符,當進程調用epoll_wait()時便獲得通知。(此處去掉了遍歷文件描述符,而是經過監聽回調的的機制。這正是epoll的魅力所在。)

注意:

若是沒有大量的idle-connection或者dead-connection,epoll的效率並不會比select/poll高不少,可是當遇到大量的idle-connection,就會發現epoll的效率大大高於select/poll。

#2 select、poll、epoll區別#

  1. 支持一個進程所能打開的最大鏈接數

輸入圖片說明

  1. FD劇增後帶來的IO效率問題

輸入圖片說明

  1. 消息傳遞方式

輸入圖片說明

綜上,在選擇select,poll,epoll時要根據具體的使用場合以及這三種方式的自身特色:

  1. 表面上看epoll的性能最好,可是在鏈接數少而且鏈接都十分活躍的狀況下,select和poll的性能可能比epoll好,畢竟epoll的通知機制須要不少函數回調。

  2. select低效是由於每次它都須要輪詢。但低效也是相對的,視狀況而定,也可經過良好的設計改善。

#讚揚個人文章給您帶來收穫# 輸入圖片說明

相關文章
相關標籤/搜索