同步阅读进度,多语言翻译,过滤屏幕蓝光,评论分享,更多完整功能,更好读书体验,试试 阅读 ‧ 电子书库
11.3 异步和事件驱动架构
事件驱动编程会一次监听不同的事件,对于组织程序流程是很好的解决方案,并不需要使用多线程的方法。
考虑这样一个程序,它想要监听一个套接字的连接,并处理收到的连接。有以下三种方式可以解决这个问题。
(1)每次有新连接建立时就创建(fork)一个新进程,需要用到multiprocessing这样的模块。
(2)每次有新连接建立时创建一个新线程,需要用到threading这样的模块。
(3)将这个新连接加入事件循环(event loop)中,并在事件发生时对其作出响应。
(现在)众所周知的是,使用事件驱动方法对于监听数百个事件源的场景的效果要好于为每个事件创建一个线程的方式4。这并不意味着二者互不兼容,这只是表明可以通过事件驱动机制摆脱多线程。
我们已经在前面讨论了前面两种选择的优劣,本节只讨论事件驱动机制。
事件驱动架构背后的技术是事件循环的建立。程序调用一个函数,它会一直阻塞直到收到事件。其核心思想是令程序在等待输入输出完成前保持忙碌状态,最基本的事件通常类似于“我有数据就绪可被读取”或者“我可以无阻塞地写入数据”。
在Unix中,用于构建这种事件循环的标准函数是系统调用select(2)或者poll(2)。它们会对几个文件描述符进行监听,并在其中之一准备好读或写时做出响应。
在Python中,这些系统调用通过select模块开放了出来。很容易用它们构造一个事件驱动系统,尽管这显得有些乏味。使用select的基本示例如示例11.4所示。
示例 11.4 使用select的基本示例
import socket
server = socket.socket(socket.AF_INET,
socket.SOCK_STREAM)
# Never block on read/write operations
server.setblocking(0)
# Bind the socket to the port
server.bind(('localhost', 10000))
server.listen(8)
while True:
# select() returns 3 arrays containing the object (sockets, files…) that
# are ready to be read, written to or raised an error
inputs, outputs, excepts = select.select(
[server], [], [server])
if server in inputs:
connection, client_address = server.accept()
connection.send("hello!\n")
不久前一个针对这些底层接口的包装器被加入到了Python中,名为asyncore。它还没有被广泛使用,而且演进也不太多。
或者,还有很多其他框架通过更为集成化的方式提供了这类功能,如Twisted(https://twistedmatrix.com/trac/)或者Tornado(http://www.tornadoweb.org/en/stable/)。Twisted多年来在这方面已经成为了事实上的标准。也有一些提供了Python接口的C语言库(如libevent、libev或者libuv)也提供了高效的事件循环。
尽管它们都能解决同样的问题,但不利的一面在于现在选择太多了,而且它们之间大多数不能互操作。而且,它们大多基于回调机制,这意味着在阅读代码时,程序的流程不是很清晰。
gevent(http://www.gevent.org/)或者Greenlet(http://greenlet.readthedocs.org/en/latest/)怎么样呢?它们没有使用回调,但实现的细节很吓人,而且包括一些CPython在x86上的特有的代码以及对标准函数的monkey补丁。如果要长期使用和维护的话实际并非好的选择。
最近,Guido Van Rossum开始致力于一个代号为tulip的解决方案,其记录在PEP 3156中。5这个包的目标就是提供一个标准的事件循环接口。将来,所有的框架和库都将与这个接口兼容,而且将实现互操作。
tulip已经被重命名并被并入了Python 3.4的asyncio包中。如果不打算依赖Python 3.4的话,也可以通过PyPI(https://pypi.python.org/pypi/asyncio)上提供的版本装在Python 3.3上,只需通过运行pip install asyncio即可安装。Victor Stinner已经开始进行移植并将tulip命名为trollius(https://pypi.python.org/pypi/trollius),目标是令其可以兼容Python 2.6及其后续版本。
现在你已经拿到了所有的牌,你肯定会想:那我到底该用什么在事件驱动的应用中构建一个事件循环呢?
在当前的Python开发中,这个问题很难回答。这门语言仍然在转换阶段。截止到本书写作时,还没有什么应用使用了asyncio模块。这意味着用了它很可能面临巨大的挑战。
下面是目前我能给出的一些建议。
如示例11.5所示,pyev的接口是很容易掌握的。通过对libev的使用,它通不但支持用于得Io对象,而且支持对子进程的跟踪,计时器、信号量和空闲时的事件回调。libev还可以自动利用polling的最好的接口,如Linux上的epoll(2)或者BSD上的kqueue(2)。
示例 11.5 pyev示例
import pyevimport socket
server = socket.socket(socket.AF_INET,
socket.SOCK_STREAM)
# Never block on read/write operations
server.setblocking(0)
# Bind the socket to the port
server.bind(('localhost', 10000))
server.listen(8)
def server_activity(watcher, revents):
connection, client_address = server.accept()
connection.send("hello!\n")
connection.close()
loop = pyev.default_loop()
watcher = pyev.Io(server, pyev.EV_READ, loop, server_activity)
watcher.start()
loop.start()
请支持我们,让我们可以支付服务器费用。
使用微信支付打赏