一文搞懂Select、Poll和Epoll區(qū)別
1 select
select本質(zhì)上是通過設(shè)置或檢查存放fd標(biāo)志位的數(shù)據(jù)結(jié)構(gòu)進(jìn)行下一步處理。
這帶來缺點(diǎn):
- 單個(gè)進(jìn)程可監(jiān)視的fd數(shù)量被限制,即能監(jiān)聽端口的數(shù)量有限
單個(gè)進(jìn)程所能打開的最大連接數(shù)有FD_SETSIZE宏定義,其大小是32個(gè)整數(shù)的大小(在32位的機(jī)器上,大小就是3232,同理64位機(jī)器上FD_SETSIZE為3264),當(dāng)然我們可以對(duì)進(jìn)行修改,然后 重新編譯內(nèi)核,但是性能可能會(huì)受到影響,這需要進(jìn)一步的測試
一般該數(shù)和系統(tǒng)內(nèi)存關(guān)系很大,具體數(shù)目可以cat /proc/sys/fs/file-max察看。32位機(jī)默認(rèn)1024個(gè),64位默認(rèn)2048。

- 對(duì)socket是線性掃描,即輪詢,效率較低:
僅知道有I/O事件發(fā)生,卻不知是哪幾個(gè)流,只會(huì)無差異輪詢所有流,找出能讀數(shù)據(jù)或?qū)憯?shù)據(jù)的流進(jìn)行操作。同時(shí)處理的流越多,無差別輪詢時(shí)間越長 - O(n)。
當(dāng)socket較多時(shí),每次select都要通過遍歷FD_SETSIZE個(gè)socket,不管是否活躍,這會(huì)浪費(fèi)很多CPU時(shí)間。如果能給 socket 注冊某個(gè)回調(diào)函數(shù),當(dāng)他們活躍時(shí),自動(dòng)完成相關(guān)操作,即可避免輪詢,這就是epoll與kqueue。
調(diào)用過程
缺點(diǎn)
內(nèi)核需要將消息傳遞到用戶空間,都需要內(nèi)核拷貝動(dòng)作。需要維護(hù)一個(gè)用來存放大量fd的數(shù)據(jù)結(jié)構(gòu),使得用戶空間和內(nèi)核空間在傳遞該結(jié)構(gòu)時(shí)復(fù)制開銷大。
- 每次調(diào)用select,都需要把fd集合從用戶態(tài)拷貝到內(nèi)核態(tài),這個(gè)開銷在fd很多時(shí)會(huì)很大
- 同時(shí)每次調(diào)用select都需要在內(nèi)核遍歷傳遞進(jìn)來的所有fd,這個(gè)開銷在fd很多時(shí)也很大
- select支持的文件描述符數(shù)量太小了,默認(rèn)是1024
2 poll
poll的實(shí)現(xiàn)和select非常相似,只是描述fd集合的方式不同,poll使用pollfd結(jié)構(gòu)而不是select的fd_set結(jié)構(gòu),其他的都差不多,管理多個(gè)描述符也是進(jìn)行輪詢,根據(jù)描述符的狀態(tài)進(jìn)行處理,但是poll沒有最大文件描述符數(shù)量的限制。poll和select同樣存在一個(gè)缺點(diǎn)就是,包含大量文件描述符的數(shù)組被整體復(fù)制于用戶態(tài)和內(nèi)核的地址空間之間,而不論這些文件描述符是否就緒,它的開銷隨著文件描述符數(shù)量的增加而線性增大。
- 它將用戶傳入的數(shù)組拷貝到內(nèi)核空間
- 然后查詢每個(gè)fd對(duì)應(yīng)的設(shè)備狀態(tài):
- 如果設(shè)備就緒,在設(shè)備等待隊(duì)列中加入一項(xiàng)繼續(xù)遍歷
- 若遍歷完所有fd后,都沒發(fā)現(xiàn)就緒的設(shè)備
掛起當(dāng)前進(jìn)程,直到設(shè)備就緒或主動(dòng)超時(shí),被喚醒后它又再次遍歷fd。這個(gè)過程經(jīng)歷多次無意義的遍歷。
沒有最大連接數(shù)限制,因其基于鏈表存儲(chǔ)
缺點(diǎn)
- 大量fd數(shù)組被整體復(fù)制于用戶態(tài)和內(nèi)核地址空間間,而不管是否有意義
- 如果報(bào)告了fd后,沒有被處理,那么下次poll時(shí)會(huì)再次報(bào)告該fd
3 epoll
可理解為event poll,epoll會(huì)把哪個(gè)流發(fā)生哪種I/O事件通知我們。所以epoll是事件驅(qū)動(dòng)(每個(gè)事件關(guān)聯(lián)fd)的,此時(shí)我們對(duì)這些流的操作都是有意義的。復(fù)雜度也降低到了O(1)。
3.1 觸發(fā)模式
EPOLLLT和EPOLLET兩種:
- LT,默認(rèn)的模式(水平觸發(fā))
只要該fd還有數(shù)據(jù)可讀,每次 epoll_wait 都會(huì)返回它的事件,提醒用戶程序去操作,
- ET是“高速”模式(邊緣觸發(fā))

只會(huì)提示一次,直到下次再有數(shù)據(jù)流入之前都不會(huì)再提示,無論fd中是否還有數(shù)據(jù)可讀。所以在ET模式下,read一個(gè)fd的時(shí)候一定要把它的buffer讀完,即讀到read返回值小于請(qǐng)求值或遇到EAGAIN錯(cuò)誤
epoll使用“事件”的就緒通知方式,通過epoll_ctl注冊fd,一旦該fd就緒,內(nèi)核就會(huì)采用類似回調(diào)機(jī)制激活該fd,epoll_wait便可收到通知。
3.2 優(yōu)點(diǎn)
- 沒有最大并發(fā)連接的限制,能打開的FD的上限遠(yuǎn)大于1024(1G的內(nèi)存上能監(jiān)聽約10萬個(gè)端口)
- 效率提升,不是輪詢,不會(huì)隨著FD數(shù)目的增加效率下降。只有活躍可用的FD才會(huì)調(diào)用callback函數(shù)
- 即Epoll最大的優(yōu)點(diǎn)就在于它只關(guān)心“活躍”的連接,而跟連接總數(shù)無關(guān),因此在實(shí)際的網(wǎng)絡(luò)環(huán)境中,Epoll的效率就會(huì)遠(yuǎn)遠(yuǎn)高于select和poll
- 內(nèi)存拷貝,利用mmap()文件映射內(nèi)存加速與內(nèi)核空間的消息傳遞;即epoll使用mmap減少復(fù)制開銷。
- epoll通過內(nèi)核和用戶空間共享一塊內(nèi)存來實(shí)現(xiàn)的
表面上看epoll的性能最好,但是在連接數(shù)少并且連接都十分活躍的情況下,select和poll的性能可能比epoll好,畢竟epoll的通知機(jī)制需要很多函數(shù)回調(diào)。
epoll跟select都能提供多路I/O復(fù)用的解決方案。在現(xiàn)在的Linux內(nèi)核里有都能夠支持,其中epoll是Linux所特有,而select則應(yīng)該是POSIX所規(guī)定,一般操作系統(tǒng)均有實(shí)現(xiàn)。
4 總結(jié)
select,poll,epoll都是IO多路復(fù)用機(jī)制,即可以監(jiān)視多個(gè)描述符,一旦某個(gè)描述符就緒(讀或?qū)懢途w),能夠通知程序進(jìn)行相應(yīng)讀寫操作。
但select,poll,epoll本質(zhì)上都是同步I/O,因?yàn)樗麄兌夹枰谧x寫事件就緒后自己負(fù)責(zé)進(jìn)行讀寫,也就是說這個(gè)讀寫過程是阻塞的,而異步I/O則無需自己負(fù)責(zé)進(jìn)行讀寫,異步I/O的實(shí)現(xiàn)會(huì)負(fù)責(zé)把數(shù)據(jù)從內(nèi)核拷貝到用戶空間。
參考
- Linux下select/poll/epoll機(jī)制的比較
- select、poll、epoll之間的區(qū)別總結(jié)[整理]