先来看看linux manual对三者的介绍
select() and pselect() allow a program to monitor multiple file descriptors, waiting until one or more of the file descriptors become "ready" for some class of I/O operation (e.g., input possible). A file descriptor is considered ready if it is possible to perform a corresponding I/O operation (e.g., read(2) without blocking, or a sufficiently small write(2)).
select() can monitor only file descriptors numbers that are less than FD_SETSIZE; poll(2) does not have this limitation
翻译一下:
select()和pselect()允许监听多个文件描述符,等待一个或多个某些IO操作文件描述符标为“就绪”状态(比如可输入)。如果一个文件描述符可以完成一个相应的IO操作(例如不阻塞的read()或者小数据量的write())。
select()只能监听少于FD_SETSIZE数量的文件描述符,poll()没有此限制,FD_SETSIZE一般为1024
select()的特点:
(1)使用fd_set这一结构体存储文件描述符,最大可监听的fd数量不能超过FD_SETSIZE
(2)每次调用select()都要将fd_set从用户空间拷贝到内核空间
(3)每次都要遍历整个fd_set,时间复杂度O(n),当fd数量很多时,耗费时间
(4)只能电平触发,如果在下一次调用select()时,上一次的事件处理函数还没处理完,即使没有新的事件,select()仍然会返回“就绪”
poll() performs a similar task to select(2): it waits for one of a set of file descriptors to become ready to perform I/O.
The set of file descriptors to be monitored is specified in the fds argument, which is an array of structures of the following form:
poll与select相似,唯一的改进就是把fd_set改成了pollfd,
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
函数原型:
int ppoll(struct pollfd *fds, nfds_t nfds, const struct timespec *tmo_p, const sigset_t *sigmask);
从上面的函数声明可以看出,调用poll()传递的参数是pollfd指针和fd数量,因此poll不再受FD_SETSIZE的限制,但是其他的问题依然存在(网上的文章都说poll用了链表来解决FD_SETSIZE限制,这个说法不对)
The epoll API performs a similar task to poll(2): monitoring multiple file descriptors to see if I/O is possible on any of them. The epoll API can be used either as an edge-triggered or a level-triggered interface and scales well to large numbers of watched file descriptors.
翻译一下:
epoll API的功能类似于poll():监听多个文件描述符是否可操作(读写)epoll可使用边沿触发或者电平触发并且可以良好的伸缩以监听大量的文件描述符。
epoll使用了三个关键的技术解决select()和poll()的不足:mmap、红黑树、回调方式检测就绪事件
(1)使用了mmap将要监听的文件描述符从用户空间映射到内核空间,避免了拷贝,其实2.5内核的epoll是需要手动mmap的,2.6之后才自动mmap
(2)使用红黑树来存储大量的fd集合,可以快速的添加、删除、修改
(3)将要监听的事件与设备驱动建立回调关系,这样就不用每次都遍历所有fd,epoll_wait()得以快速返回,另外还能实现边沿触发
因为以上原因,epoll可以监听大量的fd,比如几百万,但仍可以快速返回
不过,epoll并不是完美的,epoll的返回结果保存到一个双向链表中,需要从内核空间拷贝到用户空间才能取出监听结果,如果活跃的fd数量很大,效率会下降,特别是监听的fd数量不是很多,但非常活跃的情况,性能可能与select、poll差不多,甚至不如,这点在liveMedia555里面有体现。
epoll支持边沿触发,所以可以将epoll与线程池结合使用,于是诞生了另一种异步IO模型IOCP!
4、IOCP
IOCP是Input/Output Completion Port的缩写,是Windows、AIX、Solaris 系统中的一种高效异步IO模型,epoll只是告知调用者有哪些事件就绪,需要调用者自己去进行数据读写等操作,而IOCP除此之外还维护了一个线程池用于处理就绪的IO请求,因此IOCP可以认为是唯一一个真正意义上的异步IO
IOCP的编程复杂度要比epoll高些,更多IOCP介绍请参考MSDN文档和《WIndows核心编程》第10章
https://docs.microsoft.com/zh-cn/windows/desktop/FileIO/i-o-completion-ports
5、Kqueue
kqueue是FreeBSD系统下的一种事件通过模型,细节请参考我的另外两篇文章:
1、Kqueue用法
https://blog.csdn.net/Namcodream521/article/details/83032615
2、Kqueu的简单实例
https://blog.csdn.net/Namcodream521/article/details/83032670