Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124

同步代码在等IO时CPU空转,异步代码在等IO时去干别的事——这就是两者性能差距的根本原因。本文从实战出发,讲清楚asyncio的核心概念、常见坑、以及真实应用场景,让你从"懂语法"升级到"能实战"。
一个请求3秒,100个请求要多久?
同步做法:300秒。异步做法:可能是3秒。
差距不在代码量,在认知。
很多人学不会asyncio,不是因为它难,是因为从一开始理解错了方向。本文不讲语法讲语法,从实战出发,告诉你什么时候该用async,怎么用对async,以及async最常见的坑。
同步代码的核心特征:等。
# 同步代码
result = requests.get("https://api.example.com/data") # 程序在这里停住,等服务器回复
print(result) # 收到回复后,继续
程序在requests.get()这一行完全停住。CPU在等网络IO返回的这段时间里,什么都没干。
异步代码的核心特征:等待期间不闲着。
# 异步代码
async def fetch():
result = await requests.get("https://api.example.com/data") # 发起请求,然后去干别的事
return result
await不是”等”,是”去干别的事,等那边好了再回来”。
关键理解:异步不是让单个请求变快,是让等待的时间被利用起来。
异步有代价:代码更复杂,调试更困难。
该用async的场景:
不该用async的场景:
协程是asyncio的最小单位。本质是一个可以暂停和恢复的函数。
import asyncio
# 定义一个协程
async def hello():
print("开始")
await asyncio.sleep(1) # 暂停,去做别的事
print("结束")
# 运行协程
asyncio.run(hello())
注意:async def定义的函数是协程函数,调用它不会直接执行,会返回一个协程对象。
事件循环是asyncio的核心引擎。它的职责是:
import asyncio
# 获取当前事件循环
loop = asyncio.get_event_loop()
# 手动运行事件循环
loop.run_until_complete(hello())
Python 3.7+简化了语法:
asyncio.run(hello()) # 自动创建事件循环并运行
协程只是”可以暂停”的对象,真正让它跑起来需要Task。
async def main():
task = asyncio.create_task(hello()) # 创建Task,立即开始调度
print("我先执行")
await task # 等待hello完成
asyncio.run(main())
create_task会让协程进入事件循环,立即开始执行(而不是等到await才执行)。
多个任务并发:
async def main():
# 创建3个并发任务
tasks = [
asyncio.create_task(hello()),
asyncio.create_task(hello()),
asyncio.create_task(hello()),
]
await asyncio.gather(*tasks) # 等待所有任务完成
asyncio.run(main())
# 耗时:约1秒(3个任务并行),而不是3秒
# 错误写法
async def fetch_all():
results = [fetch(url) for url in urls] # 没有await,返回的是协程对象列表,不是结果
return results
# 正确写法
async def fetch_all():
results = await asyncio.gather(*[fetch(url) for url in urls]) # gather自动await每个协程
return results
记住:协程函数不加await调用,返回的是协程对象,不是结果。
# 错误写法
def sync_function():
result = asyncio.run(async_function()) # 可以运行,但每次都创建新的事件循环,效率极低
# 正确做法:如果是同步函数,就保持同步
async def async_caller():
result = await async_function()
return result
原则:异步代码一旦开始,尽量全链路异步。中途混用同步会失去异步优势,甚至更慢。
# 假异步(串行执行)
async def false_async():
for url in urls:
await fetch(url) # 每次await等待完成才执行下一个,跟同步没区别
# 真异步(并发执行)
async def true_async():
tasks = [fetch(url) for url in urls]
await asyncio.gather(*tasks) # 一次性发起所有请求,并发等待
判断标准:看代码里有多少个await,以及它们是在循环里还是循环外。
这是async最经典的应用场景。用aiohttp而不是requests:
import asyncio
import aiohttp
async def fetch_all(urls):
async with aiohttp.ClientSession() as session:
tasks = [fetch(session, url) for url in urls]
return await asyncio.gather(*tasks)
async def fetch(session, url):
async with session.get(url) as response:
return await response.text()
# 100个URL,总耗时约等于最慢的一个,而不是100个之和
异步数据库用asyncpg(PostgreSQL)或aiomysql(MySQL):
import asyncio
import asyncpg
async def query_many():
conn = await asyncpg.connect(host="localhost", database="mydb", user="user", password="pwd")
queries = ["SELECT * FROM users WHERE id = $1" for id in range(1, 101)]
# 并发执行100个查询
results = await asyncio.gather(*[
conn.fetch(queries[i % len(queries)]) for i in range(100)
])
await conn.close()
return results
asyncio的核心就三句话:
记住三个不要:
下一步行动:
把项目里用requests做的批量网络请求改成aiohttp,这是最容易上手、收益最明显的第一步。