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方式。