You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This discussion was converted from issue #22 on June 09, 2023 03:06.
Heading
Bold
Italic
Quote
Code
Link
Numbered list
Unordered list
Task list
Attach files
Mention
Reference
Menu
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
-
Select,回调,事件循环
对于 Python 异步编程,需要先搞清楚以下这些概念:
回调函数:提供函数供一定条件满足后调用(回调函数中都是非 I/O 操作,性能很高);
事件循环:不断循环列表请求句柄状态,发现状态变化时执行回调函数;
单线程:驱动程序运行的 loop 是单线程运行(不会有内存消耗和切换问题)、非阻塞的,只会对就绪句柄执行回调函数,不会等待 I/O(除非所有句柄都在等待)。
以一段简单的爬虫代码说明:
协程
使用以上 I/O 模型 API 存在以下问题:
回调:代码可读性差,共享状态管理困难,异常处理困难;
多线程:线程间同步、锁并发性能差,线程创建消耗内存大、切换开销大;
同步:并发度低。
因此可考虑使用协程:
采用同步的方式编写异步(事件循环 + I/O 多路复用)代码代替回调,使用单线程切换任务(不再需要锁);
自主编写调度函数,并发性能远高于线程间切换;
调度函数有多个入口:遇到 I/O 操作把当前函数暂停、切换到另一个函数执行,在适当时候恢复。
使用生成器(见“迭代器,生成器”)结合事件循环可实现协程;
协程 + 事件循环的效率不比回调 + 事件循环高,其目的在于简便地解决回调复杂的问题。
为了将语义变得更加明确,Python 3.5 后引入了 async 和 await 关键词用于定义原生协程;
使用生成器实现协程:
一般的生成器只能作为生产者,实现为协程则可以消费外部传入的数据;
使用
value = yield from xxx
的生成器表示返回值给调用方、且调用方通过 send 方法传值给生成器函数;主函数中不能添加耗时的逻辑,如把I/O操作通过
yield from
做异步处理;最终实现通过同步的方式编写异步代码:在适当时候暂停、恢复启动函数。
获取生成器的状态:
实现协程:
Asyncio 简介
asyncio 模块:asyncio 是 Python 用于解决异步 I/O 编程的整套解决方案。
高并发编程三个要素:事件循环+ I/O 多路复用 + 回调函数(驱动生成器,即协程);
包括各种特定系统实现的模块化事件循环(select、poll、epoll);
传输和协议抽象;
对TCP、UDP、SSL、子进程、延时调用以及其他的具体支持;
模仿
futures
模块但适用于事件循环使用的 Future 类;基于
yield from
的协议和任务,可以顺序的方式编写并发代码;必须使用一个将产生阻塞 I/O 的调用时,有接口可以把这个事件转义到线程池;
模仿
threading
模块中的同步原语、可以用在单线程的协程之间;关键词
async
定义协程,await
异步调用。基于asyncio的框架:tornado(实现可直接部署的 web 服务器)、gevent、twisted(scrapy,django channels),使用这些框架必须有对应的异步驱动支持(如 tornado 中使用 pymysql 则不能异步)。
asyncio 具备以下特点:
单线程,所有的函数调用都在 loop 中执行(如执行耗时操作则会等待完成后才执行下一个);
使用
await asyncio.sleep(2)
与time.sleep(2)
的区别是前者会立即返回一个 Future 对象,下次循环判断是否已经过 2s,而不是阻塞等待。如何获取协程返回值?
wait 与 gather:gather 是更高层次的封装,可以将 task 分组管理;
协程的取消,嵌套
取消future(task)
嵌套协程的调度过程(task -> print_sum -> compute):
创建 loop 和需要提交到 loop 的 task(通过 task 驱动协程执行),执行;
协程中的 await 相当于 yield from,在 task 和 compute 子协程之间建立一个通道,此时进入 compute 调度,print_sum 暂停;
子协程 compute 中的 await 表示暂停,不经过 print_sum、直接返回给 task 再返回给 loop,等待1s;
1s 过后,task 经通道询问 compute,compute 计算好结果值会抛出异常(StopIteration)并返回计算结果,compute 协程标记完成;
print_sum 捕捉到 compute 的异常、提取结果值,最后会把异常抛出给 task,print_sum 协程标记完成。
call_at,call_soon,call_later,call_soon_threadsafe
如何在协程中集成线程池?
在协程中集成阻塞 I/O:对于阻塞的库和接口(如 pymysql ),协程中要使用多线程。
模拟 HTTP 请求
requests 是同步的 http 请求模块,在 asyncio 不能达到异步的效果,而 asyncio 本身没有提供 http 协议的接口,可以使用 aiohttp:
future 和 task
见 asyncio 源码
同步与通信
实例:基于 asyncio 协程的高并发爬虫
Beta Was this translation helpful? Give feedback.
All reactions