- 内容提要
- 前言
- 作者简介
- 封面简介
- 第1章 理解高性能 Python
- 第2章 通过性能分析找到瓶颈
- 2.1 高效地分析性能
- 2.2 Julia 集合的介绍
- 2.3 计算完整的 Julia 集合
- 2.4 计时的简单方法——打印和修饰
- 2.5 用 UNIX 的 time 命令进行简单的计时
- 2.6 使用 cProfile 模块
- 2.7 用 runsnakerun 对 cProfile 的输出进行可视化
- 2.8 用 line_profiler 进行逐行分析
- 2.9 用 memory_profiler 诊断内存的用量
- 2.10 用 heapy 调查堆上的对象
- 2.11 用 dowser 实时画出变量的实例
- 2.12 用 dis 模块检查 CPython 字节码
- 2.13 在优化期间进行单元测试保持代码的正确性
- 2.14 确保性能分析成功的策略
- 2.15 小结
- 第3章 列表和元组
- 第4章 字典和集合
- 第5章 迭代器和生成器
- 第6章 矩阵和矢量计算
- 第7章 编译成 C
- 第8章 并发
- 第9章 multiprocessing 模块
- 第10章 集群和工作队列
- 第11章 使用更少的 RAM
- 第12章 现场教训
8.5 AsyncIO
作为对使用异步函数来处理重量级I/O系统的风潮的回应,Python 3.4+引入了对老式的asyncio标准库模块的改造。这个模块备受gevent和tornado并发方法的影响,定义了协程,并可以从协程切换,从而暂停当前函数的执行并允许让其他协程运行。就像在tornado中那样,事件循环显式地启动来开始执行协程。此外,Python 3引入了一个新的关键词——yield from,大大简化了对这些协程的处理(我们不再需要从一个协程中抛出异常来返回值,就如我们在例8-6中做的那样)。
值得重视的是asyncio库是很低层的,并不对用户提供更高层的功能。例如,尽管有很全面的socketAPI,但却没有简单的方法来做HTTP请求。作为结果,我们在例8-8中选择使用aiohttp。无论如何,对asyncio库的采用正开始增长,辅助模块的前景可能正在飞速变化。
例8-8 asyncio HTTP爬虫
import asyncio import aiohttp import random import string def generate_urls(base_url, num_urls): for i in range(num_urls): yield base_url + "".join(random.sample(string.ascii_lowercase, 10)) def chunked_http_client(num_chunks): semaphore = asyncio.Semaphore(num_chunks) # ❶ @asyncio.coroutine def http_get(url): # ❷ nonlocal semaphore with (yield from semaphore): response = yield from aiohttp.request('GET', url) body = yield from response.content.read() yield from response.wait_for_close() return body return http_get def run_experiment(base_url, num_iter=500): urls = generate_urls(base_url, num_iter) http_client = chunked_http_client(100) tasks = [http_client(url) for url in urls] # ❸ responses_sum = 0 for future in asyncio.as_completed(tasks): # ❹ data = yield from future responses_sum += len(data) return responses_sum if __name__ == "__main__": import time delay = 100 num_iter = 500 base_url = "http://127.0.0.1:8080/add?name=asyncio&delay={}&".format(delay) loop = asyncio.get_event_loop() start = time.time() result = loop.run_until_complete(run_experiment(base_url, num_iter)) end = time.time() print("{} {}".format(result, end-start))
❶ 正如在gevent中的例子一样,我们必须使用信号量来限制请求数量。
❷ 我们返回一个新的协程来异步下载文件,并遵从信号量的上锁。
❸ http_client函数返回futures。为了跟踪进度,我们把futures存入一个列表。
❹ 就像用gevent那样,我们能够等待futures准备就绪并去遍历它们。
asyncio模块的一个很大的好处就是与标准库相比熟悉的API,这样简化了创建辅助模块。我们能够得到与使用tornado或gevent相同类型的结果,但是如果我们想要,我们就能更深入地探索软件栈,并能用广泛的支持结构来创建我们自己的异步协议。此外,因为它是一个标准库模块,能够向我们确保这个模块总是遵照PEP并且得到了合理的维护[3]。
而且,asyncio库允许我们统一像tornado和gevent这样的模块,让它们在相同的事件循环中运行。事实上,Python 3.4版本的tornado由asyncio库作为后台支持。结果就是,尽管tornado和gevent有着不同的使用场景,底层的事件循环是统一化的,这就让从一种模式切换到其他中间代码变得轻而易举。你甚至能基于asyncio模块之上相当容易地创建你自己的包装器,为了以对你正在解决的问题可能是最有效率的方式来与异步操作交互。
尽管它只在Python 3.4或更高的版本中[4]得到支持,这个模块至少是一个伟大的标志,将来有更多的工作将放入异步I/O中。因为Python开始在越来越多的处理流水线中占主导地位(从数据处理到web请求处理),这种转变意义深远。
图8-6显示了我们HTTP爬虫的asyncio版本的请求时间线。
图8-6 例8-8的HTTP请求时间表
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论