網上現在關於這兩者不同的介紹已經到處都是了。我這裏也不能多說出什麽東西,只是記錄下我看了實現代碼之後的壹些總結。
兩者的使用場景壹般是通過壹個入口能夠同時監控多路I/O。壹般使用的接口,
epool就是
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
select為:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
通過上述兩個函數能夠將調用線程阻塞,線程變為可執行條件有兩種情況:
無任何事件發生,超時時間已過
在所控制的I/O有事件到來
epoll_wait函數中看不到相關的監控信息,因為是通過epoll_ctl已經加入,而select之間在函數調用中由(fd_set *readfds, fd_set *writefds, fd_set *exceptfds)傳入。epoll_wait飯互結果通過events返回,而select的傳入參數也是傳出參數。兩者傳出參數均表示發生事件的對應I/O標識。
兩種方式的區別主要體現在以下幾個方面:
select所能控制的I/O數有限,這主要是因為fd_set數據結構是壹個有大小的,相當與壹個定長所數組。
select每次都需要重新設置所要監控的fd_set(因為調用之後會改變其內容),這增加了程序開銷。
select的性能要比epoll差,具體原因會在後續內容中詳細說明。
嗯,說道這個為什麽select要差,那就要從這個select API說起了。這個傳進去壹個數組,內部實現也不知道那個有哪個沒有,所以要遍歷壹遍。假設說我只監控壹個文件描述符,但是他是1000。那麽select需要遍歷前999個之後再來poll這個1000的文件描述符,而epoll則不需要,因為在之前epoll_ctl的調用過程中,已經維護了壹個隊列,所以直接等待事件到來就可以了。
Linux中select此段相關代碼為:
/* 遍歷所有傳入的fd_set */
for (i = 0; i < n; ++rinp, ++routp, ++rexp) {
unsigned long in, out, ex, all_bits, bit = 1, mask, j;
unsigned long res_in = 0, res_out = 0, res_ex = 0;
const struct file_operations *f_op = NULL;
struct file *file = NULL;
in = *inp++; out = *outp++; ex = *exp++;
all_bits = in | out | ex;
/* 此處跳無需監控的fd, 白白的浪費時間啊…… */
if (all_bits == 0) {
i += __NFDBITS;
continue;
}
/* 後續進行壹些相關操作 */
}
而epoll則無需進行此類操作,直接檢測內部維護的壹個就緒隊列,如果隊列有內容,說明有I/O就緒,那麽直接賦值返回內容,成功返回,如果沒有成功,那麽睡眠,等待就緒隊列非空。
通過這個兩者的比較,其實兩者的差距啊,大部分是因為這個API設計所決定的,select就設計成這樣壹個API,內部再怎麽優化也只能是這麽個爛樣子,而epoll這樣維護與等待分離,靈活多變,最後也就帶來了相對的高性能,以及可擴展性。