IO多路複用與epoll機制淺析

epoll是Linux中用於IO多路複用的機制,在nginx和redis等軟件中都有應用,redis的性能好的緣由之一也就是使用了epoll進行IO多路複用,同時epoll也是各大公司面試的熱點問題。nginx

IO多路複用

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;        // 發生的事件
};

pollselect的主要改進就是沒有了描述符數組的大小限制,沒有最大鏈接數的限制。可是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有着如下的優勢:線程

  • 沒有最大文件描述符數量限制
  • 使用mmap,避免了每次wait都要將數組進行拷貝
  • 直接返回就緒的文件描述符,避免了遍歷,時間複雜度爲O(k),k爲就緒文件描述符
  • 使用回調機制,當文件描述符就緒時會觸發回調函數,將描述符加入到就緒鏈表,避免輪詢
  • 監聽的文件描述符數量對性能影響不大

可是epoll也不是必定比selectpoll好,當就緒的文件描述符不少時,即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方式。

相關文章
相關標籤/搜索