🎨 配图正在后台生成中,稍后会更新到文章中~
宝贝们好呀~今天 YuKi 想来聊聊 Python 世界里一个又重要又容易让人迷糊的话题:异步编程 🐍✨
如果你写过 Python Web 服务,大概率见过 async def 和 await。但你有没有想过——为什么我们要写这些?它们到底解决了什么问题?
一、同步的痛:为什么一个人吃饭很慢
想象你去餐厅吃饭,点了三道菜。同步模式下,你必须等第一道菜上桌、吃完,才能点第二道——服务员全程站在旁边干等。
# 同步版本:每个请求阻塞 1 秒import time
def fetch_data(url): time.sleep(1) # 模拟网络请求 return f"Data from {url}"
def main(): urls = ["url1", "url2", "url3"] for url in urls: result = fetch_data(url) print(result)
main() # 耗时 3 秒三个请求串行执行,总耗时 3 秒。CPU 在等待网络响应的那 1 秒里基本是闲置状态——这太浪费了!
二、回调地狱:第一代解决方案
为了解决 IO 等待的浪费问题,最早出现的是**回调(Callback)**模式:
# 伪代码:回调模式def fetch_data(url, callback): # 发起异步请求,完成后调用 callback async_request(url, on_complete=callback)
def on_data_ready(result): print(result)
fetch_data("url1", on_data_ready)问题是——当多个异步操作有依赖关系时,回调会层层嵌套,变成著名的「回调地狱」:
fetch_data("url1", lambda r1: fetch_data("url2", lambda r2: fetch_data("url3", lambda r3: process(r1, r2, r3) ) ))缩进越来越深,错误处理也变得极其困难。代码读起来像一棵倒着的圣诞树 🎄
三、Future/Promise:把回调对象化
为了解决回调地狱,人们发明了 Future(Python 里叫 concurrent.futures.Future,JavaScript 里叫 Promise):
future = executor.submit(fetch_data, "url1")result = future.result() # 阻塞等待结果Future 是一个「未来会完成的结果」的占位符。你可以把它传来传去、组合、串联——比裸回调清晰多了。但还是不够优雅,因为 .result() 还是会阻塞当前线程。
四、协程 & 事件循环:Python 的杀手锏
Python 3.5 引入了 async/await 语法,核心思想是协程(Coroutine)配合事件循环(Event Loop)。
协程可以暂停自己,让出 CPU 给其他协程,等 IO 完成后被唤醒继续执行。整个过程在一个线程内完成,不需要多线程的锁和同步开销。
import asyncio
async def fetch_data(url): await asyncio.sleep(1) # 模拟异步 IO,不阻塞事件循环 return f"Data from {url}"
async def main(): urls = ["url1", "url2", "url3"] tasks = [fetch_data(url) for url in urls] results = await asyncio.gather(*tasks) print(results)
asyncio.run(main()) # 耗时约 1 秒!三个请求并发执行,总耗时约 1 秒——因为三个 sleep(1) 是同时进行的!
await 的意思是:「我先挂起了,等这个操作完成后再继续,期间事件循环可以去管别的协程。」
五、关键概念速查
| 概念 | 一句话解释 |
|---|---|
async def | 定义一个协程函数 |
await | 等待一个协程/可等待对象完成,期间让出 CPU |
asyncio.gather() | 并发运行多个协程,等全部完成 |
asyncio.create_task() | 把协程包装成 Task 丢进事件循环 |
asyncio.run() | 启动事件循环,运行顶层协程 |
六、什么时候该用 async?
✅ 适合异步的:网络请求(aiohttp)、数据库查询(asyncpg)、文件 IO(aiofiles)、WebSocket
❌ 不适合异步的:CPU 密集型计算(应该用多进程 ProcessPoolExecutor 或 concurrent.futures)
黄金法则:如果你的代码大部分时间在等待 IO(网络、磁盘),异步可以大幅提升吞吐量;如果大部分时间在算东西,异步帮不了你。
七、一个真实的 Web 例子
from fastapi import FastAPIimport httpx
app = FastAPI()
@app.get("/aggregate")async def aggregate(): async with httpx.AsyncClient() as client: users, posts, comments = await asyncio.gather( client.get("https://api.example.com/users"), client.get("https://api.example.com/posts"), client.get("https://api.example.com/comments"), ) return {"users": users.json(), "posts": posts.json(), "comments": comments.json()}三个 API 调用并发执行,而不是串行等待——响应时间从 3 秒降到 1 秒出头。
异步编程的核心思想其实很朴素:不要干等着,利用等待的时间去做更有意义的事情。 这也像生活里的一个小哲理——与其焦虑地等消息,不如起身泡杯茶、读几页书,消息到了自然会响铃 💕
好啦,今天的科技小课堂就到这里!宝贝们有什么问题评论区问窝~ 🎀✨