select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的。而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间
select
调用后select函数会阻塞,直到有描述符就绪(有数据 可读、可写、或者有except),或者超时(timeout指定等待时间,如果立即返回设为null即可),函数返回。
当select函数返回后,可以 通过遍历fdset,来找到就绪的描述符
优点:
-
select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点。
缺点:
-
单个进程能够监视的文件描述符的数量存在最大限制,它由FD_SETSIZE设置,默认值是1024。 可以通过修改宏定义甚至重新编译内核的方式提升这一限制,但 是这样也会造成效率的降低。
-
fd集合在内核被置位过,与传入的fd集合不同,不可重用。 重复进行FD_ZERO(&rset); FD_SET(fds[i],&rset);操作
-
每次调⽤用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大。
-
同时每次调用select都需要在内核遍历传递进来的所有fd标志位,O(n)的时间复杂度,这个开销在fd很多时也很大。
poll
不同与select使用三个位图bitmap来表示三个fdset的方式,poll使用一个 pollfd的指针实现。
优点:
-
poll用pollfd数组代替了bitmap,没有最大数量限制。(解决select缺点1)
-
利用结构体pollfd,每次置位revents字段,每次只需恢复revents即可。pollfd可重用。(解决select缺点2)
缺点:
-
每次调⽤用poll,都需要把pollfd数组从用户态拷贝到内核态,这个开销在fd很多时会很大。(同select缺点3)
-
和select函数一样,poll返回后,需要轮询pollfd来获取就绪的描述符。事实上,同时连接的大量客户端在一时刻可能只有很少的处于就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降。(同select)
epoll
epoll更加灵活,没有描述符限制,epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。
每一个epoll对象都有一个独立的eventpoll结构体,用于存放通过epoll_ctl方法向epoll对象中添加进来的事件。这些事件都会挂载在红黑树中,如此,重复添加的事件就可以通过红黑树而高效的识别出来(红黑树的插入时间效率是lgn,其中n为树的高度)。
而所有添加到epoll中的事件都会与设备(网卡)驱动程序建立回调关系,也就是说,当相应的事件发生时会调用这个回调方法。这个回调方法在内核中叫ep_poll_callback,它会将发生的事件添加到rdlist双链表中。
当调用epoll_wait检查是否有事件发生时,只需要检查eventpoll对象中的rdlist双链表中是否有epitem元素即可。如果rdlist不为空,则把发生的事件复制到用户态,同时将事件数量返回给用户 。
优点:
-
监视的描述符数量不受限制
-
epoll是内核空间用一个 红黑树维护所有的fd,epoll_wait 通过回调函数内核会将 I/O 准备好的描述符加入到一个链表中管理,只把就绪的fd用链表复制到用户空间。
-
IO的效率不会随着监视fd的数量的增长而下降。epoll不同于select和poll轮询的方式,而是通过每个fd定义的回调函数来实现的