在之前的文章中 I/O復用 - epoll 和 I/O復用 - select&poll 中我們討論了三組I/O復用的系統調用,這3組系統調用都能同時監聽多個文件描述符。它們將等待由timeout參數指定的超時時間,直到一個或多個文件描述符上有事件發生時返回,返回值是就緒的文件描述符的數量。返回0表示沒有事件發生。
現在我們從事件集、最大支持文件描述符數、工作模式和具體實現等四個方面進一步比較它們的異同,以明確在實際應用中應該選擇使用哪個(或哪些)。

這三組函數都通過某種結構體變量來告訴內核監聽哪些文件描述符上的哪些條件,并使用該結構體類型的參數來獲取內核處理的結果。
select的參數類型fd_set沒有將文件描述符與事件綁定,它僅僅是一個文件描述符集合,因此select需要提供3個這種類型的參數來分別傳入和輸出可讀、可寫及異常等事件。這一方面使得select不能處理更多類型的事件,另一方面由于內核對fd_set集合的在線修改,應用程序下次調用select前不得不重置這3個fd_set集合。
poll的參數類型pollfd則多少“聰明”一些。它把文件描述符和事件都定義其中,任何事件都被統一處理,從而使得編程接口簡潔得多。并且內核每次修改的是pollfd結構體的revents成員,而events成員保持不變,因此下次調用poll時應用程序無須重置pollfd類型的事件集參數。
由于每次select和poll調用都返回整個用戶注冊的事件集合(其中包括就緒的和未就緒的),所以應用程序索引就緒文件描述符的時間復雜度是O(n)。
epoll則采用與select和poll完全不同的方式來管理用戶注冊的事件。它在內核中維護一個事件表,并提供了一個獨立的系統調用epoll_ctl來控制網其中添加、刪除、修改事件。這樣,每次epoll_wait調用都直接從該內核事件表中取得用戶注冊的事件,而無需反復從用戶空間讀入這些事件。
epoll_wait系統調用的events參數僅用來返回就緒的事件,這使得應用程序索引就緒文件描述符的時間復雜度達到O(1)。
poll和epoll_wait分別用nfds和maxevents參數指定最多監聽多少個文件描述符和事件。這兩個數值都能達到系統允許打開的最大文件描述符數目:

而select允許監聽的最大文件描述符數量通常有限制。雖然用戶可以修改這個限制,但這可能導致不可預期的后果。
select和poll都只能工作在相對低效的LT模式,而epoll則可以工作在ET高效模式,并且epoll還支持EPOLLONESHOT事件。該事件能進一步減少可讀、可寫和異常等事件被觸發的次數。
從實現原理上來說,select和poll采用的都是輪詢的方式,即每次調用都要掃描整個注冊文件描述符集合,并將其中就緒的文件描述符返回給用戶程序,因此它們檢測就緒事件的算法的時間復雜度是O(n)。epoll則不同,它采用的是回調的方式。內核檢測到就緒的文件描述符時,將觸發回調函數,回調函數就該文件描述符上對應的事件插入就緒事件隊列。內核最后在適當的時機將該就緒事件隊列中的內容拷貝到用戶空間。因此epoll_wait無須輪詢整個文件描述符集合來檢測哪些事件已經就緒,其算法時間復雜度是O(1)。
但是,當活動連接比較多的時候,epoll_wait的效率未必比select和poll高,因為此時回調函數被觸發得過于頻繁。所以epoll_wait適用于連接數量多,但活動連接較少的情況。
