文章目录
一、前言
1.I/O多路复用
- 大多数操作系统都支持select和poll
- Linux2.5+支持epoll
- BSD、Mac支持kqueue
- Solaris支持/dev/poll
- Windows支持IOCP
2.select、poll、epoll的三者的区别:
①select
目前几乎支持所有的平台。
默认单个进程能够监视的文件描述符的数量存在最大限制,在linux上默认只支持1024个socket。
可以通过修改宏定义或重新编译内核(修改系统最大支持的端口数)的方式提升这一限制。
内核准备好数据后通知用户有数据了,但不告诉用户是哪个连接有数据,用户只能通过轮询的方式来获取数据。
假定select让内核监视100个socket连接,当有1个连接有数据后,内核就通知用户100个连接中有数据了,但是不告诉用户是哪个连接有数据了,此时用户只能通过轮询的方式一个个去检查然后获取数据。
这里是假定有100个socket连接,那么如果有上万个,上十万个呢?那你就得轮询上万次,上十万次,而你所取的结果仅仅就那么1个。这样就会浪费很多没用的开销。
只支持水平触发
每次调用select,都需要把fd(文件描述符)集合从用户态拷贝到内核态,这个开销在fd很多时会很大。
同时每次调用select都需要在内核遍历所传递过来的所有fd,这个开销在fd很大时也会很大。
②poll
与select没有本质上的差别,仅仅是没有了最大文件描述符数量的限制
同样只支持水平触发
只是一个过渡版本,很少用。
③epoll
Linux2.6开始才出现的epoll,具备了select和poll的一切优先,是公认性能最好的多路I/O就绪通知方法
没有最大文件描述符数量的限制
同时支持水平触发和边缘触发
不支持windows平台
内核准备好数据以后会通知用户哪个连接有数据了。
I/O效率不随着fd数目的增加而线性下降。
使用mmap加速内核与用户空间的消息传递。
3.水平触发和边缘触发
①水平触发
将就绪的文件描述符告诉进程后,如果进程没有对其进行I/O操作,那么下次epoll时将再次报告这些文件描述符,称为水平触发。
②边缘触发
只告诉进程哪些文件描述符刚刚变为就绪状态,它只说一遍,如果没有采取行动,那么它将不会再次告知,这种方式称为边缘触发。
4.select和epoll的特点
1.select通过select()系统调用来监视多个文件描述符的数组,当select()返回后,该数组就绪的文件描述符便会被内核修改标志位,使得进程可以获得这些文件描述从而进行后续的读写操作。
由于网络响应时间的延迟使得大量TCP连接处于非活跃状态,但调用select()会对所有socket进行一次线性扫描,这也浪费了一定的开销。
2.epoll只告知那些就绪的文件描述符,而且当我们调用epoll_wait获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值,你只需要去epoll指定的一个数组中一次取得相应数量的文件描述符即可,这里也是用了内存映射技术(mmap),这样便彻底省掉了这些描述符在系统调用时复制的开销。
另一个本质的改进在意epoll采用基于时间的就绪通知方式。在select/poll中,进程只有在调用一定的方法之后,内核才对所有监视的文件描述符进行扫描,而epoll事件事先通过epoll_ctl来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait时变得到通知。
二、概述
它封装了I/O对路复用中的select和epoll,能够更快,更方便的实现多并发效果。
它定义了一个BaseSelector抽象基类,以及几个具体实现(KqueueSelector, EpollSelector…),可以等待多核文件I/O的就绪通知。
三、selectors库的一些使用方法
1.selectors库的一些常用类和方法
类的层次结构:
BaseSelector
+-- SelectSelector
+-- PollSelector
+-- EpollSelector
+-- DevpollSelector
+-- KqueueSelector
1.1 创建selector对象
class selectors.DefaultSelector是当前平台上可用的系统调用最有效的实现。
if 'KqueueSelector' in globals():
DefaultSelector = KqueueSelector
elif 'EpollSelector' in globals():
DefaultSelector = EpollSelector
elif 'DevpollSelector' in globals():
DefaultSelector = DevpollSelector
elif 'PollSelector' in globals():
DefaultSelector = PollSelector
else:
DefaultSelector = SelectSelector
注意,由于这个库没有实现windows下的IOCP, 所以windows下只能退化为select
创建一个SelectSelector实例对象
In [1]: import selectors
In [2]: selector = selectors.DefaultSelector()
...:
In [3]: selector
Out[3]