asyncio 提供了一个默认的事件循环实现,我们到目前为止一直在用。但完全可以换成别的实现,因为它们可能具有不同的特性。主要有几种方式可以换用不同的实现。一种方式是继承 AbstractEventLoop 类,实现它的方法,然后创建实例,再用 asyncio.set_event_loop 函数把它设为当前的事件循环。如果你想打造自己的定制实现,这种方法很合理。但其实市面上已经有现成的优秀实现可以用。比如下面要介绍的 uvloop。
那么,什么是 uvloop,为什么我们要用它?uvloop 是一个基于 libuv 库(https://libuv.org(https://libuv.org))的事件循环实现,而 libuv 是 node.js 运行时的基石。因为 libuv 是用 C 语言编写的,性能远超纯 Python 解释器代码。结果就是 uvloop 的速度通常比标准的 asyncio 事件循环更快,尤其在编写基于套接字和流的应用时表现尤为突出。你可以在项目在 GitHub 仓库的页面上查看性能基准测试:https://github.com/magicstack/uvloop(https://github.com/magicstack/uvloop)。需要注意的是,截至写作时,uvloop 仅在 *Nix 平台可用。
装好 libuv 后,我们就可以开始用了。我们来写一个简单的回显服务器,并使用 uvloop 的事件循环实现。
import asynciofrom asyncio import StreamReader, StreamWriterimport uvloopasync def connected(reader: StreamReader, writer: StreamWriter): line = await reader.readline() writer.write(line) await writer.drain() writer.close() await writer.wait_closed()async def main(): server = await asyncio.start_server(connected, port=9000) await server.serve_forever()uvloop.install() # ❶asyncio.run(main())
在上面的代码里,我们调用了 uvloop.install(),它会自动替换成 uvloop 事件循环。如果你不想用 install(),也可以手动这样做:
loop = uvloop.new_event_loop()asyncio.set_event_loop(loop)
关键点是,这个操作必须在调用 asyncio.run(main())之前完成。内部的 asyncio.run 会调用 get_event_loop,如果事件循环不存在,就会创建一个。如果你在正确安装 uvloop 之前就调用了它,那么系统会得到一个普通的 asyncio 事件循环,之后再装也没用。
你可能会想动手做个小实验,来测试像 uvloop 这样的事件循环是否能提升你应用的性能。uvloop 的 GitHub 项目里就有代码,可以帮你做吞吐量和每秒请求数的基准测试。
到这里,我们已经学会了如何替换为现有的事件循环实现。接下来,我们要彻底跳脱 asyncio 的束缚,亲手实现一个完全自定义的事件循环。这将帮助我们更深入地理解 asyncio 事件循环、协程、任务和未来对象是如何协同工作的。