epoll是Linux中用於IO多路複用的機制,在nginx和redis等軟件中都有應用,redis的性能好的緣由之一也就是使用了epoll進行IO多路複用,同時epoll也是各大公司面試的熱點問題。nginx
IO多路複用是一種同步IO模型,使得一個線程就能夠對多個文件描述符進行監聽。當有文件描述符準備就緒時,函數就會返回,從而通知應用進行相應的處理;當沒有描述符就緒時,函數就會阻塞。面試
IO多路複用對於網絡應用來講是很是重要的,在沒有IO多路複用時,應用通常經過同步阻塞(每一個socket鏈接創建一個新線程,這將十分耗費系統性能)或者同步非阻塞(對全部socket進行反覆遍歷,當沒有就緒描述符時就會作無用功)來實現,而這些方法的性能都不太好。redis
在Linux中,IO多路複用主要有三種方法select、poll和epoll。數組
select
int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
select
是經過傳遞文件描述符數組fd_set*
來實現的。當沒有描述符準備就緒時,函數就會阻塞;當有一個或多個文件描述符準備就緒時就會返回,以後經過遍歷數組找到準備就緒的描述符進行處理。select
函數通常在全部操做系統中都會實現,所以具備良好的可移植性。網絡
fd_set
的大小是固定的,在Linux中通常爲1024,本質是一個bitmap,經過FD_SET
將描述符加入fd_set
,經過對全部文件描述符依次調用FD_ISSET
來判斷是否準備就緒。socket
所以,select
就有着如下的缺點:函數
select
的文件描述符最大隻能支持1024個select
須要經過遍從來判斷是否準備就緒,所以時間複雜度爲O(n)select
內核態中經過輪詢來判斷文件描述符是否就緒select
每次調用都須要將fd_set
從用戶地址空間拷貝到內核地址空間中,函數返回時又要拷貝回來poll
int poll(struct pollfd *fds, nfds_t nfds, int timeout); struct pollfd { int fd; // 文件描述符 short events; // 等待的事件 short revents; // 發生的事件 };
poll
對select
的主要改進就是沒有了描述符數組的大小限制,沒有最大鏈接數的限制。可是poll
仍然須要進行遍歷才能知道哪些文件描述符準備就緒,所以,select
的缺點poll
也有。性能
epoll
int epoll_create(int size); int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
epoll
使用了三個系統調用來實現,epoll_create
建立一個句柄,epoll_ctl
向句柄中添加、刪除或修改文件描述符,epoll_wait
對句柄進行監聽,當有文件描述符準備就緒後,就會經過events
參數返回。返回的參數中僅包含準備就緒的文件描述符,也就是說再也不須要經過遍從來進行判斷。epoll
經過回調機制來快速將文件描述符加入就緒鏈表,避免輪詢;同時epoll
內部使用紅黑樹來保存全部監聽的文件描述符。操作系統
epoll
有着如下的優勢:線程
wait
都要將數組進行拷貝可是epoll
也不是必定比select
和poll
好,當就緒的文件描述符不少時,即O(k)中的k接近n時,二者性能就比較接近了;當文件描述符數量較少時,二者性能也差很少;epoll
的回調函數註冊也會帶來必定的性能開銷。
epoll
有兩種觸發方式,水平觸發(LT, level-triggered)和邊緣觸發(ET, edge-triggered)。經過一個例子來理解兩種方式:
當描述符a中到達2kb數據,調用epoll_wait
會返回a,以後從描述符中讀取1kb數據,此時該描述符中仍有1kb數據,仍爲就緒狀態;第二次調用epoll_wait
時,若是是LT,那麼返回的描述符中仍包含a,若是爲ET,那麼就不包含a。
即ET只會在狀態發生改變時觸發,只返回一次,相似於上升沿觸發;而LT只要處於就緒狀態就會一直返回,相似於電平觸發。
理論上ET的性能會比LT要好,可是ET要保證每次都要把數據所有處理完成,而LT使用起來就更加方便,不易出現bug。在實際當中兩種的性能區別能夠忽略,redis使用的就是LT方式。