Python 标准库 18.3

孤人 提交于 2019-12-12 13:48:12

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

select 模块一般有两个主要对象 —— select 函数和 Polling 对象。select 一般各平台都会有,而 Polling 是区分平台实现的,比如在 Linux 上他就是 epoll,在 Solaris 上叫做 devpoll

selectepoll 实际都是访问系统调用,功能是等待 I/O 完成。

select


select(rlist, wlist, xlist[, timeout]) 函数接受三个文件描述符的列表作为参数,并将其中就绪的文件描述符返回。如果当前没有就阻塞调用。以 socket 为例,假设有两对 socket 对象 s1s2, 都没有未读取的数据,那么:

lang:python
>>> import select
>>> x = select.select([s1, s2], [], [])
...

就会阻塞。直到彼端的 socket 发送数据:

# 这里是另一端的 s1 和 s2
>>> s1.send(b'hello')

此端的 select 调用就会返回:

>>> x
([s1], [], [])
>>> x[0][0].recv(100)
b'hello'

epoll


Polling Object 是对各平台上此类对象的一个统称,下面皆用 Linux 的实现 epoll 指代。

epoll 的功能和 select 一样,但是算法效率更高,并发处理能力也更大。epoll 还可以支持很多种 eventmask,即触发事件不仅限于 select 的可读、可写、可执行。比如它的触发方式就可以分为 边界触发(edge-triggered)电平触发(level-triggered)。边界触发的条件是文件描述符发生实际状态变化,而电平触发取决于文件描述符当前是否满足条件。如上例中,假如最后调用的是 s1.recv(4) 返回一个 b'hell',即缓冲里还剩余了一个字节没有读。如果这时调用epoll.poll(),那么边界触发就不会生效,而电平触发则会。

这些触发事件是通过二进制掩码来定义的,并作为常量保存在 select 模块里。可以这样查看:

>>> import select
>>> for name in dir(select):
...		if name.isupper():
...			print(name, bin(getattr(select, name))
...
EPOLLERR 0b1000
EPOLLET 0b10000000000000000000000000000000
EPOLLHUP 0b10000
EPOLLIN 0b1
EPOLLMSG 0b10000000000
EPOLLONESHOT 0b1000000000000000000000000000000
EPOLLOUT 0b100
EPOLLPRI 0b10
EPOLLRDBAND 0b10000000
EPOLLRDNORM 0b1000000
EPOLLWRBAND 0b1000000000
EPOLLWRNORM 0b100000000
EPOLL_CLOEXEC 0b10000000000000000000
PIPE_BUF 0b1000000000000
POLLERR 0b1000
POLLHUP 0b10000
POLLIN 0b1
POLLMSG 0b10000000000
POLLNVAL 0b100000
POLLOUT 0b100
POLLPRI 0b10
POLLRDBAND 0b10000000
POLLRDNORM 0b1000000
POLLWRBAND 0b1000000000
POLLWRNORM 0b100000000

其中需要注意的是,EPOLLET 作为边界触发的一个掩码,是要与其他普通码一起来用的。即 EPOLLET 无法被单独触发,比如想定义文件描述符第一次处于可读状态这个事件,就要把 poll 的 eventmask 参数写成 EPOLLIN|EPOLLET。类似的还有 EPOLONESHOT。例:

>>> sock.fileno()
99
>>> epoll.register(sock, EPOLLIN)
# 此时彼端 sock.send(b'hello')
>>> epoll.poll()
[(99, 1)]
>>> epoll.poll()
[(99, 1)]

这里连续调用两次 poll,都可以不阻塞地得到 sock 的 fileno 和 eventmask,是因为默认的触发方式是 level-triggered

>>> sock.fileno()
99
>>> epoll.register(sock, EPOLLIN|EPOLLET)
# 此时彼端 sock.send(b'hello')
>>> epoll.poll()
[(99, 1)]
>>> epoll.poll()
...

这里将 eventmask 设置为 edge-triggered 式后,第一次调用 poll() 还可以触发事件,但第二次就阻塞了。

EpollSelector


EpollSelectorselectors 模块的一个类。selector 构建于 select 之上,提供更加便于操作和高性能的 IO 复用接口。因为 select 的实现区分平台,所以 selectors 提供了一个 DefaultSelector 名字指向当前平台最佳的 Selector 类。

Selector 对象提供的方法与 Polling Object 类似,主要是 .register(fileobj, events, data=None)unregister(fileobj)select(timeout=None)。其中 register 接受一个特别的 data 参数,这是一个与 fileobj 关联的对象,会在 select 调用后与 fileobj 一起放在 SelectorKey 中返回。比如一个回调函数。

Selectors 官方示例

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!