Python 高级 I/O 多路复用
1 前言
2 select
介绍 I/O 多路复用这个概念往往都是从 select
这个 系统调用 开始的,这篇博客也不例外,但是为了方便,我们使用的是 Python 提供的 select.select
函数2。
这个函数的参数列表与返回值是这样的:
select(rlist, wlist, xlist[, timeout]) -> (rlist, wlist, xlist)
各个参数的含义:
rlist
- 等待 可读 事件发生的文件描述符列表wlist
- 等待 可写 事件发生的文件描述符列表xlist
- 等待 异常 事件发生的文件描述符列表timeout
- 可选,指定最长阻塞等待时间
调用这个函数后会 阻塞 等待和各个的文件描述符相对应的 I/O 事件发生,然后将准备好的文件描述符返回。
I/O 多路复用的基本模型就和这个函数一样,都是通过一定的方式监听处理 多个 文件的 I/O 事件,或者说,select 是 I/O 多路复用的一种实现。
除了 select
以外,常见的还有 poll
和 epoll
这两个实现,它们背后的基本思想都是一样的,区别主要就在于使用的方便性和效率上。
3 selectors
selectors
模块是对 select
的一层封装,能够让我们以更方便的方式来使用 I/O 多路复用。
简单的使用流程为:
通过
selectors.DefaultSelector()
创建默认的selector
当然还存在其他不同种类的
Selector
, 但是不同种类的Selector
的受支持情况在不同的平台上存在差异,因此大多数时候使用DefaultSelector
就可以了,它是相应平台上最有效的Selector
的易名。通过
selector.register(fileobj, events, data=None)
方法注册需要监听的 文件描述符 或 文件对象可以看到,在使用
selector
时我们可以注册 文件对象 而不一定必须要是 文件描述符3,这为我们的使用带来了一定的便利。对于参数
events
, 在selectors
模块中定义的事件只有EVENT_READ
和EVENT_WRITE
, 刨除了不太常用的 异常 事件。如果要同时注册两个事件可以像这样:
selector.register(fileobj, EVENT_READ | EVENT_WRITE)
调用
selector.select(timeout=None)
方法监听并获取发生了的 键 - 事件 对列表这里的键是指
SelectorKey
对象,它的属性包括:fileobj
- 我们注册的文件对象fd
- 我们注册的文件对象的文件描述符events
- 我们注册的事件data
- 注册时的data
参数
处理得到的 键 - 事件 对列表
如果注册了不同类型的事件,处理时可以通过返回的事件类型进行处理。
同时,我们可以在注册时通过
data
参数附带回调函数,方便处理。
下面是官网的例子:
import selectors import socket sel = selectors.DefaultSelector() def accept(sock, mask): conn, addr = sock.accept() # Should be ready print('accepted', conn, 'from', addr) conn.setblocking(False) sel.register(conn, selectors.EVENT_READ, read) def read(conn, mask): data = conn.recv(1000) # Should be ready if data: print('echoing', repr(data), 'to', conn) conn.send(data) # Hope it won't block else: print('closing', conn) sel.unregister(conn) conn.close() sock = socket.socket() sock.bind(('localhost', 1234)) sock.listen(100) sock.setblocking(False) sel.register(sock, selectors.EVENT_READ, accept) while True: events = sel.select() for key, mask in events: callback = key.data callback(key.fileobj, mask)
4 结语
虽然接触 I/O 多路复用已经有一段时间了,但是还没有遇到过需要使用的地方……
或者说需要使用的地方已经被相关类库的作者封装好了,没自己什么事了 @_@
但是,掌握相关的概念还是很有价值的。
5 参考链接
脚注:
C 语言版可以参考 select - Wikipedia
文件描述符可以通过文件对象的 fileno()
方法获取