Python异步编程asyncio完全指南:从零到实战,告别阻塞等待
引言
你是否遇到过这样的场景:程序需要同时下载多个网页、处理大量I/O请求,但用普通同步代码时,每个请求都要等待上一个完成,整个程序慢得像老牛拉车?Python的asyncio就是为解决这类问题而生的。它让你能用“单线程+事件循环”的方式,写出高效、易维护的并发程序。
本文将从核心概念出发,配合完整的实战爬虫示例,帮你彻底搞懂asyncio。即使你是异步编程新手,也能跟着代码一步步运行起来,感受异步带来的速度提升。
一、核心概念:协程、事件循环与可等待对象
1. 协程(coroutine)
协程是asyncio的核心。你可以把它理解为一种特殊的函数:它可以在中途“暂停”,把控制权交还给调度器,等条件满足后再从暂停点恢复执行。Python中定义协程非常简单:
import asyncio
async def my_coroutine():
print("开始执行")
await asyncio.sleep(1) # 模拟异步等待
print("等待结束")
关键点:
- 使用async def定义。
- 内部使用await挂起当前协程,等待另一个可等待对象完成。
2. 事件循环(Event Loop)
事件循环是asyncio的发动机。它负责调度和运行所有协程,处理I/O事件、定时任务等。你可以把它想象成一个不停循环的“管家”,检查哪些协程可以继续执行,并快速切换。
平时使用时,我们一般不需要直接创建事件循环,asyncio.run()会帮我们搞定一切:
asyncio.run(my_coroutine()) # 自动创建事件循环并运行
3. 可等待对象(Awaitable)
能在await后面使用的对象,被称为“可等待对象”。主要分为三类:
- 协程(Coroutines):用async def定义的函数对象。
- 任务(Tasks):用来并发执行多个协程。
- Future:底层对象,代表一个异步操作的最终结果,通常不需要直接创建。
当你在一个协程里await另一个可等待对象时,当前协程会挂起,直到等待的对象完成并返回结果。
二、创建多个任务:并发执行
如果只是顺序await多个协程,那和同步没有区别。要实现真正的并发,需要将协程包装成Task,交给事件循环同时调度。
1. 使用asyncio.create_task()
import asyncio
import time
async def fetch_data(task_id, delay):
print(f"任务{task_id}开始,等待{delay}秒...")
await asyncio.sleep(delay)
print(f"任务{task_id}结束")
return f"数据{task_id}"
async def main():
# 创建两个任务,它们会并发运行
task1 = asyncio.create_task(fetch_data(1, 2))
task2 = asyncio.create_task(fetch_data(2, 1))
print("任务已创建,等待完成...")
# 等待任务完成并获取结果
result1 = await task1
result2 = await task2
print(f"获得结果: {result1}, {result2}")
start = time.time()
asyncio.run(main())
print(f"总耗时: {time.time() - start:.2f}秒")
运行结果:
任务已创建,等待完成...
任务1开始,等待2秒...
任务2开始,等待1秒...
任务2结束
任务1结束
获得结果: 数据1, 数据2
总耗时: 2.00秒
可以看到,两个任务总耗时只有2秒(最长等待时间),而不是3秒(顺序执行的总和)。这正是并发的魅力。
2. 使用asyncio.gather()
gather可以同时运行多个可等待对象,并返回结果列表:
async def main():
results = await asyncio.gather(
fetch_data(1, 2),
fetch_data(2, 1),
fetch_data(3, 0.5)
)
print(f"所有结果: {results}")
gather的方便之处在于,你不需要逐一创建Task,它会自动把协程包装成任务执行。如果其中一个任务抛出异常,默认会立即传播,也可以通过return_exceptions=True将异常当作正常结果返回。
三、实战示例:异步爬虫,速度对比
下面我们写一个完整的异步爬虫,利用aiohttp并发请求多个URL,并与同步版本对比速度。
环境准备:
pip install aiohttp
```python
import asyncio
import time
import aiohttp
import requests
要爬取的URL列表(示例用了几个公开的接口)
URLS = [
"https://httpbin.org/delay/1",
"https://httpbin.org/delay/2",
"https://httpbin.org/delay/1",
"https://httpbin.org/delay/3",
]
更多推荐
所有评论(0)