当前位置: 首页 > news >正文

Python 使用 asyncio 包处理并 发(避免阻塞型调用)

避免阻塞型调用

Ryan Dahl(Node.js 的发明者)在介绍他的项目背后的哲学时说:“我们
处理 I/O 的方式彻底错了。” 他把执行硬盘或网络 I/O 操作的函数定义
为阻塞型函数,主张不能像对待非阻塞型函数那样对待阻塞型函数。
为了说明原因,他展示了表 18-1 中的前两列。

表18-1:使用现代的电脑从不同的存储介质中读取数据的延迟情
况;第三栏按比例换算成具体的时间,便于人类理解

image
为了理解表 18-1,请记住一点:现代的 CPU 拥有 GHz 数量级的时钟频
率,每秒钟能运行几十亿个周期。假设 CPU 每秒正好运行十亿个周
期,那么 CPU 可以在一秒钟内读取 L1 缓存 333 333 333 次,读取网络 4
次(只有 4 次)。表 18-1 中的第三栏是拿第二栏中的各个值乘以固定
的因子得到的。因此,在另一个世界中,如果读取 L1 缓存要用 3 秒,
那么读取网络要用 7.6 年!

有两种方法能避免阻塞型调用中止整个应用程序的进程:

  • 在单独的线程中运行各个阻塞型操作
  • 把每个阻塞型操作转换成非阻塞的异步调用使用
    多个线程是可以的,但是各个操作系统线程(Python 使用的是这种线
    程)消耗的内存达兆字节(具体的量取决于操作系统种类)。如果要处
    理几千个连接,而每个连接都使用一个线程的话,我们负担不起。

为了降低内存的消耗,通常使用回调来实现异步调用。这是一种低层概
念,类似于所有并发机制中最古老、最原始的那种——硬件中断。使用
回调时,我们不等待响应,而是注册一个函数,在发生某件事时调用。
这样,所有调用都是非阻塞的。因为回调简单,而且消耗低,所以 Ryan
Dahl 拥护这种方式。

当然,只有异步应用程序底层的事件循环能依靠基础设置的中断、线
程、轮询和后台进程等,确保多个并发请求能取得进展并最终完成,这
样才能使用回调。 事件循环获得响应后,会回过头来调用我们指定的
回调。不过,如果做法正确,事件循环和应用代码共用的主线程绝不会
阻塞。

把生成器当作协程使用是异步编程的另一种方式。对事件循环来说,调
用回调与在暂停的协程上调用 .send() 方法效果差不多。各个暂停的
协程是要消耗内存,但是比线程消耗的内存数量级小。而且,协程能避
免可怕的“回调地狱”;这一点会在 18.5 节讨论。

现在你应该能理解为什么 flags_asyncio.py 脚本的性能比 flags.py 脚本高
5 倍了:flags.py 脚本依序下载,而每次下载都要用几十亿个 CPU 周期
等待结果。其实,CPU 同时做了很多事,只是没有运行你的程序。与此
相比,在 flags_asyncio.py 脚本中,在 download_many 函数中调用
loop.run_until_complete 方法时,事件循环驱动各个
download_one 协程,运行到第一个 yield from 表达式处,那个表达
式又驱动各个 get_flag 协程,运行到第一个 yield from 表达式处,调用 aiohttp.request(…) 函数。这些调用都不会阻塞,因此在零
点几秒内所有请求全部开始。

asyncio 的基础设施获得第一个响应后,事件循环把响应发给等待结果
的 get_flag 协程。得到响应后,get_flag 向前执行到下一个 yield
from 表达式处,调用 resp.read() 方法,然后把控制权还给主循环。
其他响应会陆续返回(因为请求几乎同时发出)。所有 get_ flag 协
程都获得结果后,委派生成器 download_one 恢复,保存图像文件。

为了尽量提高性能,save_flag 函数应该执行异步操作,可
是 asyncio 包目前没有提供异步文件系统 API(Node 有)。如果
这是应用的瓶颈,可以使用 loop.run_in_executor 方法
(https://docs.python.org/3/library/asyncioeventloop.
html#asyncio.BaseEventLoop.run_in_executor),在线程池
中运行 save_flag 函数。示例 18-9 会说明做法。

因为异步操作是交叉执行的,所以并发下载多张图像所需的总时间比依
序下载少得多。我使用 asyncio 包发起了 600 个 HTTP 请求,获得所
有结果的时间比依序下载快 70 倍。

现在回到那个 HTTP 客户端示例,看看如何显示动态的进度条,并且恰
当地处理错误。

http://www.lryc.cn/news/601186.html

相关文章:

  • 创建属于自己的github Page主页
  • 【自动化运维神器Ansible】Ansible常用模块之archive模块详解
  • github上传本地项目过程记录
  • 【C语言网络编程基础】DNS 协议与请求详解
  • STM32的蓝牙通讯(HAL库)
  • 飞牛NAS本地化部署n8n打造个人AI工作流中心
  • 用 Flask 打造宠物店线上平台:从 0 到 1 的全栈开发实践
  • idea总结
  • JVM工具
  • 【优选算法】BFS解决FloodFill算法
  • Element表格单元格类名动态设置
  • VILA系列论文解读
  • 基于mnn架构在本地 c++运行llm与mllm模型
  • PostgreSQL AND OR 操作符详解
  • esp32s3创建rust工程 window成功mac
  • 前后端分离:架构模式与实践
  • Qt 分裂布局:QSplitter 使用指南
  • 四、搭建springCloudAlibaba2021.1版本分布式微服务-加入openFeign远程调用和sentinel流量控制
  • UNet 改进(38):融合多尺度输入与可变形卷积、门控特征融合的医学图像Unet分割网络
  • MySQL 事务和锁
  • 02人工智能中优雅草商业实战项目视频字幕翻译以及声音转译之以三方AI模型API制作方式预算-卓伊凡|莉莉
  • 车载诊断架构 ---面向售后的DTC应该怎么样填写?
  • KNN算法实战:手写数字识别详解
  • 前端基础班学习路线
  • Git+宝塔面板部署Hugo博客
  • net8.0一键创建辅助开发的个人小工具
  • 剑指offer第2版:双指针+排序+分治+滑动窗口
  • 零基础学习性能测试第五章:JVM性能分析与调优-GC垃圾分代回收机制与优化
  • 【嵌入式硬件实例】-555定时器调光电路实现
  • 工业控制系统安全之 Modbus 协议中间人攻击(MITM)分析与防范