epoll跟select都能提供多路I/O複用的解決方案。在現在的Linux核心裡有都能夠支援,其中epoll是Linux所特有,而select則應該是POSIX所規定,一般作業系統均有實現。
網上現在關於這兩者不同的介紹已經到處都是了。我這裡也不能多說出什麼東西,只是記錄下我看了實現代碼之後的一些總結。
兩者的使用情境一般是通過一個入口能夠同時監控多路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這樣維護與等待分離,靈活多變,最後也就帶來了相對的高效能,以及可擴充性。
以後的代碼生涯中,還是先要設計好API,然後才能寫出好代碼……(轉自:http://blog.chinaunix.net/uid-23146151-id-3112631.html)