首先,我們來看看同步和異步。
在處理 IO 的時候,阻塞和非阻塞都是同步 IO。
只有使用了特殊的 API 才是異步 IO。
接下來,我們來看看Linux下的三大同步IO多路復用函數
fcntl(fd, F_SETFL, O_NONBLOCK); //socket設為O_NONBLOCK,但是select/poll/epoll是block操作
1)select
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
缺點:
1). nfds有限制,最大只能是1024
2). 知道有事件到來,還要遍歷到底是哪個fd觸發的
3).不能動態的修改fdset, 或者關閉某個socket
4)、需要維護一個用來存放大量fd的數據結構,這樣會使得用戶空間和內核空間在傳遞該結構時復制開銷大
優點:(select如此的老,我們還需要它嗎?)
1). 更好的兼容老的系統
2). select 計算超時可以達到納秒精度,而poll, epoll只能達到毫秒的精度。client/server用不着這么高,但嵌入式系統用的着。
事實上,如果你寫一個不超過200個socket程序,select和poll, epool性能上沒啥區別。
2)poll
int poll(struct pollfd *fds,nfds_t nfds, int timeout);
缺點:
1). 知道有事件到來,還要遍歷到底是哪個fd觸發的
2).不能動態的修改fdset, 或者關閉某個socket
優點:
1)沒有fd個數限制
使用場景:
1)多平台支持,不僅僅是linux,你又不想使用libevent
2)不超過1000個sockets
3)超過1000個socket,但是socket都是short-lived的
4)Your application is not designed the way that it changes the events while another thread is waiting for them
3)epool (epoll is Linux only)
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
1)Your application runs a thread poll which handles many network connections by a handful of threads.
單線程應用使用epool得不償失,不比pool好
2)至少1000個以上的socket,socket
3)epoll是Linux內核為處理大批量文件描述符而作了改進的poll,是Linux下多路復用IO接口select/poll的增強版本,它能顯著提高程序在大量並發連接中只有少量活躍的情況下的系統CPU利用率。另一點原因就是獲取事件的時候,它無須遍歷整個被偵聽的描述符集,只要遍歷那些被內核IO事件異步喚醒而加入Ready隊列的描述符集合就行了。epoll除了提供select/poll那種IO事件的水平觸發(Level Triggered)外,還提供了邊緣觸發(Edge Triggered),這就使得用戶空間程序有可能緩存IO狀態,減少epoll_wait/epoll_pwait的調用,提高應用程序效率。
怎么選擇呢?
如果處理的連接數不是很高的話,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延遲還更大。
select/epoll的優勢並不是對於單個連接能處理得更快,而是在於能處理更多的連接。真正的異步IO(下面會統一叫做AIO)應該像Windows IOCP一樣,傳入文件句柄,緩存區,尺寸等參數和一個函數指針,當操作系統真正完成了IO操作,再執行對應的函數。
實際上對於socket來說,epoll已經是最高效的模型了,雖然比AIO多一次recv系統調用,但總體來看沒有任何IO等待,效率很高。而且epoll是天然的reactor模型,程序實現更容易。AIO如windows的IOCP,是異步回調的方式,開發難度很高。
再來說Reactor首先看它的類圖,順便說一句,我向來看不懂類圖,我只喜歡序列圖
下面上序列圖:
OK,搞定,看懂了么? 看不懂慢慢看吧