epoll为什么这么高效?

Posted zkccpro

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了epoll为什么这么高效?相关的知识,希望对你有一定的参考价值。

epoll为什么这么高效?

先说结论:我觉得不是epoll有多高效,而是linux中其他的多路IO复用(select/poll)的接口有多低效。。。

select和poll更像是一个内核提供给你的小玩具,帮你入门多路IO复用。真正生产级别的工具是epoll。

首先需要复习一下IO复用是啥呢?

简单说 就是用一个句柄同时监控多个IO的状态(某一时刻是否可以非阻塞读写?)。如此,我只要查看这一个句柄的情况就可以处理很多个IO,从而在恰当的时机进行有效的非阻塞读写,避免用户态反复遍历多个IO,十分低效。

这就需要IO复用接口的底层帮我们高效地处理多个IO的响应,如果IO复用底层处理得不妥(比如每次调用接口都需要遍历所有IO),时间复杂度就上去了,带来的问题就是整个IO复用的效率就很低。

一、select和poll的底层是怎么做的?

当在用户态调用select时:

  1. 从用户空间拷贝fd_set到内核空间,用户态进程休眠,进入select内核态进程。
  2. 为所有fd注册回调函数
  3. 遍历所有fd,调用2中注册好的回调函数
  4. 每个fd的回调函数做的事情:把select进程(内核进程)加入该fd对应设备的等待队列中,当该设备可读写时,就会把select进程从等待队列中取出并唤醒。
  5. 遍历过程中,如果有设备就绪,则设置对应fd_set,唤醒用户态进程。
  6. 如果遍历完后没有设备就绪,则select进程进入休眠,经过超时时间后,继续苏醒遍历(重复3->5)。

想象一下,在用户态使用select是需要不断循环调用的,select苏醒并处理好事务后再次调用。。而每次调用select,都需要拷贝fd集合、遍历所有fd。。这种O(n)复杂度的操作每次都会做,显然会对服务并发度产生不良影响!

poll的做法和select类似不需过多了解,可见,select和poll的这种做法(每次请求到来时都要分一个进程做O(n)操作),当并发请求数(qps)上涨时,请求处理时间也随之上涨,并发性能直线下降。。。

二、epoll底层是怎么做的?

说epoll的具体做法之前,先总结一下select和poll的缺点:

  1. 每次调用都要拷贝所有fd集合到内核进程。
  2. 每次调用都要遍历所有fd,判断每个fd当前是否准备就绪。

所以,epoll优化的目标就很明确了,就是针对以上两点进行优化改进:

  1. 在请求到来前的初始化阶段就完成fd的拷贝。
  2. 处理并发请求时不需每次都遍历所有fd,把每次请求处理的时间复杂度压缩到O(n)以下。

我们都知道epoll的基本用法:有3个系统调用:epoll_create(创建epoll句柄),epoll_ctl(增删改事件),epoll_wait(阻塞进行IO分发)。接下来我们逐一看看这三个调用在内核中具体做的事情:

  • epoll_create:

    除了创建句柄,还会在内核epoll进程空间中创建一个红黑树,用于存放所有的事件信息:事件类型、fd、数据指针;创建一个双向链表用于存储当前就绪的事件。

  • epoll_ctl:

    对用户设置的事件进行增删改,可以在请求到来前调用,也可以在请求到来后调用。因此,查找事件的效率很重要,这时红黑树就派上用场了。把所有事件都存到红黑树里,epoll_ctl对事件进行查找以及增删改操作就更快了。

    另外,对于增加事件的操作,epoll_ctl还会负责将epoll进程注册到该事件对应的设备上。(select中2的操作)因此,在请求到来时就不需要一次性注册所有fd了。

    可见,epoll_ctl的好处是可以避免了每次请求到来时的拷贝耗时以及注册fd的耗时。

  • epoll_wait:

    开始阻塞用户线程,等待IO就绪并返回。与select/poll不同的是,epoll_wait并不需要每次遍历所有fd以等待触发回调。在内核态中是一个**”异步读写“的过程:回调函数如果就绪了,唤醒epoll进程的同时会将就绪的IO事件放到双向链表**。因此每次epoll进程醒过来只要检查双向链表里是否有事件即可。

    于是,每次调用epoll_wait时,epoll进程的时间复杂度只要O(1),和n无关。于是epoll就可以抗住百万级别的并发,并发数增多,epoll_wait的耗时也不会随之增长。

    这种异步读写的思想,在实际工程中也十分常用,通过这种异步操作有效地去除了依赖关系,是提高整个系统并发度的良好模式。(比如,在这里因为epoll线程异步获取IO状态而不需轮询之,这就提升了整个操作系统的并发度)

    epoll_wait异步获取各IO设备的状态示意

以上是关于epoll为什么这么高效?的主要内容,如果未能解决你的问题,请参考以下文章

epoll为什么这么高效?

从I/O复用谈epoll为什么高效

linux下的epoll怎样高效处理百万连接

linux下epoll如何实现高效处理百万句柄的

epool如何高效

epoll详细工作原理(转)