1,先贴一下函数原型及说明:
def select(rlist, wlist, xlist, timeout=None): # real signature unknown; restored from __doc__
"""
select(rlist, wlist, xlist[, timeout]) -> (rlist, wlist, xlist)
Wait until one or more file descriptors are ready for some kind of I/O.
The first three arguments are sequences of file descriptors to be waited for:
rlist -- wait until ready for reading
wlist -- wait until ready for writing
xlist -- wait for an ``exceptional condition''
If only one kind of condition is required, pass [] for the other lists.
A file descriptor is either a socket or file object, or a small integer
gotten from a fileno() method call on one of those.
The optional 4th argument specifies a timeout in seconds; it may be
a floating point number to specify fractions of seconds. If it is absent
or None, the call will never time out.
The return value is a tuple of three lists corresponding to the first three
arguments; each contains the subset of the corresponding file descriptors
that are ready.
*** IMPORTANT NOTICE ***
On Windows, only sockets are supported; on Unix, all file
descriptors can be used.
windows上只支持sockets套接字文件描述符,Unix支持所有的文件描述符
"""
pass
def poll(*args, **kwargs): # real signature unknown
"""
Returns a polling object, which supports registering and
unregistering file descriptors, and then polling them for I/O events.
"""
pass
class epoll(object): #它是个类,而select和poll是方法。
"""
select.epoll(sizehint=-1, flags=0)
Returns an epolling object
sizehint must be a positive integer or -1 for the default size. The
sizehint is used to optimize internal data structures. It doesn't limit
the maximum number of monitored events.
"""
2,通俗地说说IO多路复用的作用
条件未满足而等待就是阻塞,非阻塞就是条件未满足但不等待。
我们都知道,一般涉及IO的操作默认都是阻塞的,像input()、套接字的accept、对文件IO的操作read/write都是阻塞的,当一个程序中涉及多个IO操作而这些IO操作又或者不相关又或者相关但允许异步时,为了不让整个程序阻塞在某一个IO操作上而不能及时地处理其它就绪IO,我们想到了集中监控的方法,即在应用层只设置一个总代理(select/poll/epoll),而总代理负责监控所有IO(在内核层实现集中监控),这样应用层就只阻塞(也可设置不阻塞哈)等待这一个总代理的报告就好了,这样应用层就能及时的处理所有就绪IO了。(在应用层自己轮询IO是不明智的,内核已经用更高明高效的方法实现了)
更官方的说法:
IO多路复用:
同时监控多个IO事件,当哪个IO事件准备就绪就执行哪个IO事件。以此形成可以同时处理多个IO的行为,避免一个IO阻塞造成其他IO均无法执行,提高了IO执行效率。
3,select通用性更强一点,因为大部分操作系统都实现了它,具体情况如下:
select方法 : windows linux unix
poll方法: linux unix
epoll方法: linux
4,sellect、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,只告诉进程哪些文件描述符刚刚变为就绪状态,它只说一遍,如果我们没有采取行动,那么它将不会再次告知,这种方式称为边缘触发,注意边缘触发并不是将事件丢弃,而是在下一次新的事件到来时将旧的未处理的事件一并带过来,大概理解为啥叫边缘触发了),理论上边缘触发的性能要更高一些,但是代码实现相当复杂。
在水平触发下,如果我们没有采取行动,再次调用sellect、poll、epoll时它们会直接返回,因为未处理事件依然会上报。
from _socket import *
from select import *
from socket import socket
sc = socket(family=AF_INET, type=SOCK_STREAM, proto=0)
sc.bind(("127.0.0.1",8888))
sc.listen(5)
rlist=[sc]
while True:
rs,ws,xs=select(rlist,[],[])
print(rs)
c,addr = sc.accept() #把处理套接字的accept注释掉试试,每次循环调用到select时都会直接返回,因未处理事件一直在上报(rs里面一直有数据)。
# print("Connect from",addr)
epoll同样只告知那些就绪的文件描述符,而且当我们调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值,你只需要去epoll指定的一个数组中依次取得相应数量的文件描述符即可,这里也使用了内存映射(mmap)技术,这样便彻底省掉了这些文件描述符在系统调用时复制的开销。
另一个本质的改进在于epoll采用基于事件的就绪通知方式。在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。
QZ:即减少了应用层和内核层的交互数据量,从而减少了应用层的遍历次数。提高了效率。
5,同步IO使用IO多路复用有点SB,没必要嘛!一般还是异步IO使用IO多路复用。
同步:只能按照顺序执行,必须执行完上一步才能进行下一步操作,不可跳过
异步:异步是指不必等待上一步执行完,而允许跳过去继续执行下面的操作。