select epoll

关于select与epoll

两种IO模型,都属于多路IO就绪通知,提供了对大量文件描述符就绪检查的高性能方案,只不过实现方式有所不一样:linux

select:apache

一个select()系统调用来监视包含多个文件描述符的数组,当select返回,该数组中就绪的文件描述符便会被内核修改标志位。数组

select的 跨平台 作的很好,几乎每一个平台都支持。 服务器

select缺点有如下三点:网络

  1. 单个进程可以 监视的文件描述符的数量存在最大限制
  2. select()所维护的 存储大量文件描述符的数据结构 ,随着文件描述符数量的增加,其在用户态和内核的地址空间的复制所引起的开销也会线性增加
  3. 因为网络响应时间的延迟使得大量TCP链接处于非活跃状态,但调用select()仍是会对 全部的socket进行一次线性扫描 ,会形成必定的开销

poll:数据结构

poll是unix沿用select本身从新实现了一遍,惟一解决的问题是poll 没有最大文件描述符数量的限制多线程

epoll:架构

epoll带来了两个优点,大幅度提高了性能:并发

  1. 基于事件的就绪通知方式 ,select/poll方式,进程只有在调用必定的方法后,内核才会对全部监视的文件描述符进行扫描,而epoll事件经过epoll_ctl()注册一个文件描述符,一旦某个文件描述符就绪时,内核会采用相似call back的回调机制,迅速激活这个文件描述符,epoll_wait()便会获得通知
  2. 调用一次epoll_wait()得到就绪文件描述符时,返回的并非实际的描述符,而是一个表明就绪描述符数量的值,拿到这些值去epoll指定的一个数组中依次取得相应数量的文件描述符便可,这里使用内存映射(mmap)技术, 避免了复制大量文件描述符带来的开销

固然epoll也有必定的局限性, epoll只有Linux2.6才有实现 ,而其余平台都没有,这和apache这种优秀的跨平台服务器,显然是有些背道而驰了。socket

select的本质是采用32个整数的32位,即32*32= 1024来标识,fd值为1-1024。当fd的值超过1024限制时,就必须修改FD_SETSIZE的大小。这个时候就能够标识32*max值范围的fd。

对于单进程多线程,每一个线程处理多个fd的状况,select是不适合的。

1.全部的线程均是从1-32*max进行扫描,每一个线程处理的均是一段fd值,这样作有点浪费

2.1024上限问题,一个处理多个用户的进程,fd值远远大于1024

因此这个时候应该采用poll,

poll传递的是数组头指针和该数组的长度,只要数组的长度不是很长,性能仍是很不错的,由于poll一次在内核中申请4K(一个页的大小来存放fd),尽可能控制在4K之内

epoll仍是poll的一种优化,返回后不须要对全部的fd进行遍历,在内核中维持了fd的列表。select和poll是将这个内核列表维持在用户态,而后传递到内核中。可是只有在2.6的内核才支持。

epoll更适合于处理大量的fd ,且活跃fd不是不少的状况,毕竟fd较多仍是一个串行的操做

=====================================================================

对select、poll、epoll了解得很少,下面是从《构建高性能Web站点》摘录下来的介绍,等之后真正接触到select、poll和epoll方面的开发再详细写一下使用上的区别。

select

select最先于1983年出如今4.2BSD中,它经过一个select()系统调用来监视多个文件描述符的数组,当select()返回后,该数组中就绪的文件描述符便会被内核修改标志位,使得进程能够得到这些文件描述符从而进行后续的读写操做。

select目前几乎在全部的平台上支持,其良好跨平台支持也是它的一个优势,事实上从如今看来,这也是它所剩很少的优势之一。

select的一个缺点在于单个进程可以监视的文件描述符的数量存在最大限制,在Linux上通常为1024,不过能够经过修改宏定义甚至从新编译内核的方式提高这一限制。

另外,select()所维护的存储大量文件描述符的数据结构,随着文件描述符数量的增大,其复制的开销也线性增加。同时,因为网络响应时间的延迟使得大量TCP链接处于非活跃状态,但调用select()会对全部socket进行一次线性扫描,因此这也浪费了必定的开销。

poll

poll在1986年诞生于System V Release 3,它和select在本质上没有多大差异,可是poll没有最大文件描述符数量的限制。

poll和select一样存在一个缺点就是,包含大量文件描述符的数组被总体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增长而线性增大。

另外,select()和poll()将就绪的文件描述符告诉进程后,若是进程没有对其进行IO操做,那么下次调用select()和poll()的时候将再次报告这些文件描述符,因此它们通常不会丢失就绪的消息,这种方式称为水平触发(Level Triggered)。

epoll

直到Linux2.6才出现了由内核直接支持的实现方法,那就是epoll,它几乎具有了以前所说的一切优势,被公认为Linux2.6下性能最好的多路I/O就绪通知方法。

epoll能够同时支持水平触发和边缘触发(Edge Triggered,只告诉进程哪些文件描述符刚刚变为就绪状态,它只说一遍,若是咱们没有采起行动,那么它将不会再次告知,这种方式称为边缘触发),理论上边缘触发的性能要更高一些,可是代码实现至关复杂。

epoll一样只告知那些就绪的文件描述符,并且当咱们调用epoll_wait()得到就绪文件描述符时,返回的不是实际的描述符,而是一个表明就绪描述符数量的值,你只须要去epoll指定的一个数组中依次取得相应数量的文件描述符便可,这里也使用了内存映射(mmap)技术,这样便完全省掉了这些文件描述符在系统调用时复制的开销。

另外一个本质的改进在于epoll采用基于事件的就绪通知方式。在select/poll中,进程只有在调用必定的方法后,内核才对全部监视的文件描述符进行扫描,而epoll事先经过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用相似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便获得通知。

 

epoll的优势:

1.支持一个进程打开大数目的socket描述符(FD)
select 最不能忍受的是一个进程所打开的FD是有必定限制的,由FD_SETSIZE设置,默认值是2048。对于那些须要支持的上万链接数目的IM服务器来讲显然太少了。这时候你一是能够选择修改这个宏而后从新编译内核,不过资料也同时指出这样会带来网络效率的降低,二是能够选择多进程的解决方案(传统的 Apache方案),不过虽然linux上面建立进程的代价比较小,但仍旧是不可忽视的,加上进程间数据同步远比不上线程间同步的高效,因此也不是一种完美的方案。不过 epoll则没有这个限制,它所支持的FD上限是最大能够打开文件的数目,这个数字通常远大于2048,举个例子,在1GB内存的机器上大约是10万左右,具体数目能够cat /proc/sys/fs/file-max察看,通常来讲这个数目和系统内存关系很大。

2.IO效率不随FD数目增长而线性降低
传统的select/poll另外一个致命弱点就是当你拥有一个很大的socket集合,不过因为网络延时,任一时间只有部分的socket是"活跃"的,可是select/poll每次调用都会线性扫描所有的集合,致使效率呈现线性降低。可是epoll不存在这个问题,它只会对"活跃"的socket进行操做---这是由于在内核实现中epoll是根据每一个fd上面的callback函数实现的。那么,只有"活跃"的socket才会主动的去调用 callback函数,其余idle状态socket则不会,在这点上,epoll实现了一个"伪"AIO,由于这时候推进力在os内核。在一些 benchmark中,若是全部的socket基本上都是活跃的---好比一个高速LAN环境,epoll并不比select/poll有什么效率,相反,若是过多使用epoll_ctl,效率相比还有稍微的降低。可是一旦使用idle connections模拟WAN环境,epoll的效率就远在select/poll之上了。

3.使用mmap加速内核与用户空间的消息传递。
这点实际上涉及到epoll的具体实现了。不管是select,poll仍是epoll都须要内核把FD消息通知给用户空间,如何避免没必要要的内存拷贝就很重要,在这点上,epoll是经过内核于用户空间mmap同一块内存实现的。而若是你想我同样从2.5内核就关注epoll的话,必定不会忘记手工 mmap这一步的。

4.内核微调
这一点其实不算epoll的优势了,而是整个linux平台的优势。也许你能够怀疑linux平台,可是你没法回避linux平台赋予你微调内核的能力。好比,内核TCP/IP协议栈使用内存池管理sk_buff结构,那么能够在运行时期动态调整这个内存pool(skb_head_pool)的大小--- 经过echo XXXX>/proc/sys/net/core/hot_list_length完成。再好比listen函数的第2个参数(TCP完成3次握手的数据包队列长度),也能够根据你平台内存大小动态调整。更甚至在一个数据包面数目巨大但同时每一个数据包自己大小却很小的特殊系统上尝试最新的NAPI网卡驱动架构。

 

Apache与Nginx:

Apache与Nginx的性能谁更高效,取决于其服务器的并发策略以及其面对的场景:

并发策略:

咱们目前使用的 Apache是基于一个线程处理一个请求的非阻塞IO并发策略 。这种方式容许一个进程中经过多个线程来处理多个链接,其中每一个线程处理一个链接。Apache使用其worker模块实现这种方式,目的是减小perfork模式中太多进程的开销,使得apache能够支持更多的并发链接。

至于,非阻塞IO的实现,是经过一个子进程负责accept(),一旦接收到链接后,便将任务分配给适当worker的线程。

因为apache的线程使用的是内核进程调度器管理的轻量级进程,所以与perfork模式比较,进程上下文切换的开销依然存在,性能提高不是很明显。

Nginx使用的是一个进程处理多个链接、非阻塞IO模式 ,这种模式最特别的是设计了独立的listener进程,专门负责接收新的链接,再分配给各个worker,固然为了减小任务调度的开销,通常都是由worker进程来进行接收。

而IO模型层面,Nginx选择epoll,此方式高效主要在于其基于事件的就绪通知机制,在高链接数的场景下,epoll通知方式更具优点。另外,epoll方式只关注活跃链接,而不像select方式须要扫描全部的文件描述符,这样在大量链接的场景下,epoll方式优点会更加明显。

相关文章
相关标签/搜索