3

python异步编程之asyncio初识 - 金色旭光

 8 months ago
source link: https://www.cnblogs.com/goldsunshine/p/17935368.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

image

1|0async await介绍

用asyncio提供的@asyncio.coroutine可以把一个生成器标记为协程类型,然后在协程内部用yield from 等待IO操作,让出cpu执行权。
然而异步的关键字yield 和 yield from毕竟是复用生成器关键字,两者在概念上纠缠不清,所以从Python 3.5开始引入了新的语法async和await替换yield 和 yield from,让协程的代码更易懂。
简单来说,可以这样理解:

  • async 替换 @asyncio.coroutine:标识一个函数为异步函数
  • await 替换 yield from:标识等待IO操作,让出CPU执行权

2|0async 实现协程示例

由于协程在各个python版本中有细微差异,本篇以python3.10为例



import asyncio

async def coro1(): print("start coro1") await asyncio.sleep(2) print("end coro1")

async def coro2(): print("start coro2") await asyncio.sleep(1) print("end coro2")

# 创建事件循环 loop = asyncio.get_event_loop()

# 创建任务 task1 = loop.create_task(coro1()) task2 = loop.create_task(coro2())

# 运行协程 loop.run_until_complete(asyncio.gather(task1, task2))

# 关闭事件循环 loop.close()

输出结果:



start coro1 start coro2 end coro2 end coro1

代码逻辑:

  1. 创建一个事件循环
  2. 将两个异步函数coro1,coro2封装成两个任务task1,task2
  3. 用asyncio.gather将两个任务组合到一起,并发执行task1,task2
  4. 先执行task1,遇到IO切换到task2
  5. 执行task2,遇到IO切换,但此时没有等待执行的任务,cpu为空
  6. task2执行完成,task1执行完成

从示例代码可以看出,协程的几个关键要素:

  1. 协程函数定义
  2. 可等待对象

3|0协程基本原理

组成协程最重要的因素就是事件循环任务

  • 任务就是一个对象,包括执行的代码,执行完成、失败等状态以及返回结果,任务中通常会有IO切换。
  • 事件循环,可以把它当做是一个while循环。while循环在周期性的运行并执行一些任务,所有任务执行完成会关闭循环。

伪代码示例如下:



任务列表 = [ 任务1, 任务2, 任务3,... ]

while True: 可执行的任务列表,已完成的任务列表 = 去任务列表中检查所有的任务,将'可执行'和'已完成'的任务返回 for 就绪任务 in 已准备就绪的任务列表: 执行已就绪的任务 for 已完成的任务 in 已完成的任务列表: 在任务列表中移除 已完成的任务

如果 任务列表 中的任务都已完成,则终止循环

获取和创建事件循环:loop = asyncio.get_event_loop()
驱动事件循环运行:loop.run_until_complete(asyncio.gather(task1, task2))
事件循环过程:
事件循环中执行任务,当执行到某一个任务时遇到IO时,协程会让出CPU给第二个任务执行,第二个任务中遇到IO再次让出CPU,直到所有任务完成。这就是协程并发性能好的一个关键能力:遇到IO切换任务执行,避免了程序等待IO完成再执行的耗时。

4|0示例代码的高级api实现

示例代码中使用了asyncio.get_event_loop()loop.run_until_complete()等代码,这些其实asyncio包的低级API,是为了展示底层原理而使用的。通常更推荐高级APIasyncio.run()实现协程并发。



import asyncio

async def coro1(): print("start coro1") await asyncio.sleep(2) print("end coro1")

async def coro2(): print("start coro2") await asyncio.sleep(1) print("end coro2")

async def main(): task1 = asyncio.create_task(coro1()) task2 = asyncio.create_task(coro2()) await asyncio.gather(task1, task2)

asyncio.run(main())

run() 从功能上等价于以下低阶API



loop = asyncio.get_event_loop() task = loop.create_task(coro()) loop.run_until_complete(task)

5|0为什么协程在IO密集时性能较好

很多人可能会疑问,多线程遇到IO也会切换,为什么协程比线程性能好呢?
简单来是三点:

  1. 协程更轻量级,切换需要恢复的上下文很少,所以比线程更快速
  2. 线程切换CPU是抢占的,协程是主动让出的,协程对CPU的使用更充分
  3. 协程更轻量级,启动线程需要的内存资源比协程更多

连载一系列关于python异步编程的文章。包括同异步框架性能对比、异步事情驱动原理等。欢迎关注微信公众号第一时间接收推送的文章。

1060878-20230517204518176-1503947723.png

__EOF__


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK