丘奕奕
2025-7-5 09:49:44
转载学习:Python: 从async/await理解协程的高效世界
在Python编程的领域中,异步处理就像一位高效的时间管理大师,能让程序在处理IO密集型任务时大幅提升效率。Python 3.5引入的async和await语法糖,让原本复杂的异步编程变得清晰易懂。接下来,我们就通过日常生活中的例子和简单代码,深入理解协程、async/await及asyncio框架的核心概念。
- IO密集型任务:指任务执行过程中大部分时间花费在等待输入/输出(I/O)操作完成(如网络请求、文件读写、数据库查询等)。在此期间,CPU处于空闲状态,无需进行大量计算。典型场景包括:
- 网页爬虫(频繁发起网络请求并等待响应)
- 文件批量读写(等待磁盘数据传输)
- 数据库交互(等待查询结果返回)
- CPU密集型任务:指任务执行过程中需要大量CPU计算资源,几乎不涉及I/O等待(如数据加密/解密、图像渲染、科学计算等)。此时CPU持续处于高负载状态,主要用于逻辑运算或数值处理。典型场景包括:
- 视频编码/解码
- 大规模数据统计分析
- 复杂数学模型运算
协程是什么?
想象你来到图书馆,准备度过充实的一天,想一边查阅资料,一边完成练习题。
- 进程:就好比整个图书馆,是程序运行的环境,里面有各种资源和空间供任务开展。
- 线程:像是图书馆里不同的自习室,每个自习室可以容纳不同的人做不同的事,它们相对独立又共享图书馆的资源。
- 协程:则是你本人,在等资料打印出来(IO 等待)的时间里,不会干坐着,而是立刻开始做练习题;当练习题遇到不会的,又切换去看看资料是否打印好。哪件事先有结果(资料打印好了或者做出了一道题)就先处理哪件。这就是协程的核心 —— 在单线程内灵活切换任务,避免因等待IO操作而浪费时间。
协程的关键特性:
- 在单线程内执行,任务切换成本极低
- 特别适合处理网络请求、文件读写这类IO密集型任务
- 无法利用多核CPU,处理CPU密集型任务需要搭配多进程
异步和同步
同步执行(串行):
你先去资料室提交打印申请,然后守在打印机旁,直到资料打印完,才回到座位开始做练习题。整个过程总耗时 = 等资料打印的20分钟 + 做练习题的30分钟 = 50分钟。
异步执行(并行):
你提交打印申请后,不需要一直等着,立刻回到座位开始做练习题。在做题过程中,资料打印好了就去取,取完资料继续做题。总耗时 = 30分钟(最长任务时间)。
核心差异:
- 同步:任务按顺序依次执行,必须等前一个任务完成才能进行下一个
- 异步:任务启动后不阻塞后续操作,通过回调或事件通知任务完成
async/await
- import asyncio
- # 用async声明异步函数
- async def print_material(duration):
- # await 等待异步操作,不阻塞线程
- await asyncio.sleep(duration)
- print(f'打印材料耗时{duration}秒')
复制代码- asyncio.run(print_material(2))
复制代码
- async def main():
- # 创建多个任务
- task1 = asyncio.create_task(print_material(2))
- task2 = asyncio.create_task(print_material(3))
- task3 = asyncio.create_task(print_material(1))
- # 等待所有任务完成
- await asyncio.gather(task1, task2, task3)
- asyncio.run(main())
复制代码
其中:await只能用于可等待对象(如asyncio.sleep()),直接使用time.sleep()会阻塞线程。asyncio.create_task()用于注册并发任务,asyncio.gather()可批量等待任务完成
协程和线程
实际案例:
- 下载 1000 张图片: 协程方案能在单线程内高效完成,线程方案创建大量线程容易导致资源耗尽
- 数据加密计算: 线程配合多进程可以利用多核CPU,协程无法提升计算速度
asyncio框架
asyncio是Python内置的异步框架,它就像一个智能管家,负责管理协程的执行流程,其内部实现异步的原理主要包含以下几个关键部分:
1. 事件循环(Event Loop)
事件循环是 asyncio 的核心,它就像是一个不停运转的 “调度员”。这个调度员手中有一张任务清单,不断地检查清单里的任务是否有可以执行的。当某个协程遇到await时,就会暂停执行,并把执行权交回事件循环。事件循环会记录下这个协程暂停的位置,然后去检查其他任务。一旦await后面的异步操作完成(比如asyncio.sleep()时间结束),事件循环就会把这个协程重新放到任务清单中,等待合适的时机继续执行。
比如,在前面图书馆的例子中,你在等待资料打印时开始做题,这个 “切换任务” 的动作就像是事件循环在调度。当资料打印好这个事件发生,事件循环就会提醒你去取资料。- import asyncio
- async def task1():
- print("任务1开始执行")
- await asyncio.sleep(2)
- print("任务1执行完毕")
- async def task2():
- print("任务2开始执行")
- await asyncio.sleep(1)
- print("任务2执行完毕")
- async def main():
- loop = asyncio.get_event_loop()
- task_list = [loop.create_task(task1()), loop.create_task(task2())]
- await asyncio.gather(*task_list)
-
- asyncio.run(main())
复制代码
其中,事件循环loop负责调度task1和task2,根据await asyncio.sleep()的时间,灵活安排任务的执行顺序。
asyncio.Task是对协程的进一步封装,它代表一个具体的异步任务。当我们使用asyncio.create_task()创建任务时,实际上是将协程包装成了一个Task对象,并加入到事件循环的任务队列中。Task对象有自己的状态(如 pending、running、done 等),事件循环通过这些状态来管理任务的执行。
Future则用于表示异步操作的最终结果。当一个协程完成时,会将结果存储在对应的Future对象中。比如,当一个网络请求的协程获取到数据后,数据就会存放在相关的Future里,其他协程可以通过await这个Future来获取结果。
asyncio支持为任务添加回调函数。当一个异步任务完成时,事件循环会自动调用预先设置的回调函数。这就好比你点外卖时,设置了外卖送达时的提醒,外卖送到后手机就会触发提醒(回调函数执行)。- import asyncio
- def callback(future):
- print(f"任务结果:{future.result()}")
- async def async_task():
- await asyncio.sleep(3)
- return "任务完成"
- async def main():
- task = asyncio.create_task(async_task())
- task.add_done_callback(callback)
- await task
- asyncio.run(main())
复制代码 其中,当async_task执行完毕,callback函数就会被调用,输出任务的结果。
await关键字是实现协程挂起和恢复的关键。当协程执行到await时,会暂停当前协程的执行,并将控制权交回事件循环。此时,事件循环可以去执行其他可运行的协程。当await后面的操作完成,事件循环会恢复该协程的执行,从暂停的位置继续运行。这种机制使得在单线程内可以高效地处理多个异步任务,避免了因等待IO而造成的线程阻塞。
核心概念:
- 事件循环(Event Loop):异步任务的调度中心,不断检查任务状态
- 任务(Task):对协程的封装,用于管理执行状态
- Future:表示异步操作的最终结果,可通过await获取
来源:豆瓜网用户自行投稿发布,如果侵权,请联系站长删除 |
|
|
|
相关推荐
|
|