select,poll,epoll都是IO多路复用的机制。I/O多路复用就通过一种机制来监视多个描述符,一旦某个描述符就绪(读或写),就能够通知程序进行相应的读写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的。在高并发服务器中,轮询I/O是最耗时间的操作之一。
1. select
select 的本质是通过设置或检查存放fd 标志位的数据结构进行下一步的操作,select的调用复杂度是线性的,每次以O(n)的时间遍历一遍所有的对象。它的缺点如下:
- 对 socket 的扫描都是线性的,即每一个都要轮询一遍,效率低。当IO事件发生时,却不知道是哪个流或是哪几个流,只会无差异地轮询所有留,找出能进行读或写操作的流来。监视的流越多,需要的处理时间越长。
- 除了慢之外,单个进程所能监视的fd数量也会受到限制,单个进程所能打开的最大连接数量由
FD_SETSIZE宏定义。32位机器中默认1024个,64位机器中默认2048个。 - 需要维护一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大。
2.poll
poll 本质上和select没有区别,也是通过轮询的方式来进行处理,只是用的存储结构不同。它是基于链表来存储的,所以与select相比没有最大连接数量的限制。但是它仍然需要把大量的fd数组复制于用户态和内核地址空间之间,而不论这些文件描述符是否就绪。开销随着fd的数量增加而线性增大。
3.epoll
epoll可以理解为event poll,epoll会把哪个流发生了怎样的I/O事件通知我们。
epoll提供了三个函数,epoll_create,epoll_ctl和epoll_wait,epoll_create是创建一个epoll句柄;epoll_ctl是注册要监听的事件类型;epoll_wait则是等待事件的产生。
epoll 有两种触发机制: EPOLLLT 和 EPOLLET ,LT是默认的模式,ET是高速模式。
- LT模式下,只要这个fd还有数据可读,每次
epoll_wait都会返回它的事件,提醒用户程序去操作. - 在ET模式中,它只会提示一次,直到下次再有数据流入之前都不会再提示了,无论fd中是否还有数据可读。
epoll使用“事件”的就绪通知方式,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,epoll_wait便可以收到通知。因此,epoll的这种机制,能够高效的处理更多的并发连接,而且性能不会随着连接数增加而下降。
优点:
- 没有最大并发连接的限制。
- 不需要进行轮询,效率提升,也不会随着fd数量的增加而降低效率。只有活跃的fd才会调用
callback函数。 - 内存拷贝,利用
mmap()文件映射内存加速与内核空间的消息传递,减少复制开销
4.总结
- 支持最大连接数:
- select:由
FS_SETSIZE宏定义,在32位的机器上,大小就是1024,同理64位机器上FD_SETSIZE为2048。 - poll:poll采用链表存储,没有最大连接数量的限制。
- epoll: 虽然连接数有上限,但是很大。
- select:由
- fd增加后带来的IO 效率问题
- select/poll:因每次调用都要轮询所有的fd,因此效率会线性下降。
- select:是根据每个fd上的回调函数实现的,只有活跃的fd才会调用callback。但是当所有socket都活跃的情况下,可能会有性能问题。
- 消息传递机制
- select/poll:内核需要将消息传递到用户空间,需要内核拷贝动作。
- epoll:通过内核和用户空间共享内存来实现。
5. 同步、异步、阻塞、非阻塞
同步:进程执行 IO 操作后,进程等待或者轮询地查看 IO 操作是否完成,等待结果,才能执行后续的操作。
异步:进程执行 IO 操作后直接返回去做自己的事情, IO 交给内核处理,处理完成后再通知进程。
阻塞:进程执行 IO 操作后要一直等待操作完成才能执行后续的操作。
非阻塞:进程执行 IO 操作后,继续处理后续操作,不会被阻塞执行。过后再来询问操作是否完成。
同步有阻塞和非阻塞之分,而异步一定是非阻塞的。
同步和异步是针对应用程序来说的,关注的是程序之间的协作关系;阻塞和非阻塞关注的是单个进程的执行状态。
参考链接