当前位置:首页>python>5分钟搞懂 Python 异步编程:从回调地狱到 async/await

5分钟搞懂 Python 异步编程:从回调地狱到 async/await

  • 2026-09-30 11:30:25
5分钟搞懂 Python 异步编程:从回调地狱到 async/await
关注+星标,每天学习Python新技能

来源:网络

做后端开发最怕遇到什么?我问过身边不少程序员,答案出奇一致——异步代码。

同步代码写久了,一上来就看到 async def、await,脑子里全是问号。今天这篇文章,我用最直白的语言,把 Python 异步编程的核心逻辑讲清楚。

为什么需要异步?

先说一个真实场景。

你写了一个爬虫,要从10个网站抓数据。同步写法是这样的:

import requestsdeffetch_all():    results = []for url in urls:        resp = requests.get(url)  # 每个请求阻塞等待        results.append(resp.json())return results

假设每个请求平均耗时1秒,10个网站就要10秒。但如果这10个请求可以同时发出,理论上1秒就能搞定。

异步编程解决的就是这个问题——在等待 I/O(网络请求、文件读写、数据库查询)的时候,让 CPU 去干别的事。

三个核心概念

1. async / await:协程的语法糖

Python 3.5 引入了这两个关键字,这是现代异步代码的标准写法:

import asyncioasyncdefhello():print("Hello")await asyncio.sleep(1)  # 暂停1秒,但不阻塞其他任务print("World")# 运行协程asyncio.run(hello())

async def 定义一个协程函数,调用它不会立即执行,而是返回一个协程对象。await 用来等待另一个协程完成,期间把控制权交出去,让其他任务有机会运行。

2. asyncio:事件循环

asyncio 是 Python 内置的异步库,核心是一个事件循环(Event Loop)。

你可以把它理解成一个"调度员",它同时管理多个协程的运行状态。当某个协程在 await 时让出 CPU,调度员立刻切换到另一个就绪的协程继续执行。

import asyncioasyncdeftask(name, seconds):print(f"{name} 开始,等待 {seconds} 秒")await asyncio.sleep(seconds)print(f"{name} 完成")asyncdefmain():# 创建两个任务,同时执行await asyncio.gather(        task("任务A", 2),        task("任务B", 1),    )asyncio.run(main())# 输出:# 任务A 开始,等待 2 秒# 任务B 开始,等待 1 秒# 任务B 完成# 任务A 完成

注意看执行顺序:任务A和任务B同时启动,B只等1秒所以先完成。整个过程只用了2秒,而不是3秒。

3. 协程 vs 线程:谁更快?

有人会说,"多线程不也能并发吗?"

能。但区别很大:

对比项
多线程
协程(asyncio)
切换方式
操作系统抢占式
协程主动让出
资源消耗
每个线程占用约 8MB 栈空间
每个协程只有几百字节
并发数量
几百个已是极限
上万个轻松应对
适用场景
CPU 密集型
I/O 密集型

线程虽好,但创建成本高、切换开销大。当你的程序大量时间花在等待网络响应时,协程是更优雅的选择。

实战:用 aiohttp 写异步爬虫

光说不练假把式,来一个真实可用的例子。

同步爬虫(10个网站串行请求):

import requestsimport timedefsync_crawl(urls):    start = time.time()    results = []for url in urls:        r = requests.get(url, timeout=5)        results.append(r.json())print(f"同步耗时:{time.time() - start:.2f}s")return results

异步爬虫:

import aiohttpimport asyncioimport timeasyncdefasync_crawl(urls):    start = time.time()asyncwith aiohttp.ClientSession() as session:asyncdeffetch(url):asyncwith session.get(url) as resp:returnawait resp.json()        results = await asyncio.gather(*[fetch(url) for url in urls])print(f"异步耗时:{time.time() - start:.2f}s")return results

实际测试中,10个网站:

  • 同步:约 10 秒
  • 异步:约 1 秒

性能差距立竿见影。

常见坑:不要在异步里调同步代码

这是新手最容易踩的坑。

asyncdefbad_example():# ❌ 千万别这样写!import pandas as pd    df = pd.read_csv("data.csv")  # pandas 是同步库,会阻塞整个事件循环return dfasyncdefgood_example():# ✅ 用 asyncio.to_thread 把同步操作放到线程池import pandas as pddefread_csv():return pd.read_csv("data.csv")    df = await asyncio.to_thread(read_csv)return df

原则只有一条:异步代码里,只用异步库。遇到必须用同步库的场景,用 asyncio.to_thread() 包装一下,让它在后台线程里执行。

异步生态:这些库你迟早会用到

场景
同步库
异步替代
HTTP 请求
requests
aiohttp / httpx
数据库连接
pymysql / psycopg2
aiomysql / asyncpg
文件操作
open()
aiofiles
Redis
redis-py
aioredis
API 框架
Flask / Django
FastAPI(天生异步)

如果你正在用 FastAPI 开发 API,服务默认就是异步的,搭配这些异步库使用,性能直接拉满。

写在最后

异步编程的本质,是让等待的时间被充分利用。一旦理解了这个逻辑,再看 async/await 就不会觉得神秘了。

我个人的经验是:先把 asyncio.gather 用熟,能并发执行多个协程就已经解决了80%的场景。等真正遇到复杂任务调度时,再去深入研究信号量、队列、任务组这些进阶用法。

学编程没有捷径,但有正确的方法。动手写一个异步爬虫,跑出结果的那一刻,你会和我一样觉得——原来异步也就这么回事。

长按或扫描下方二维码,免费获取 Python公开课和大佬打包整理的几百G的学习资料,内容包含但不限于Python电子书、教程、项目接单、源码等等

▲扫描二维码-免费领取

推荐阅读

8个Python高效数据分析的技巧!(附完整代码)

掌握这20个 SQL 优化方法,你就无敌了!

10个强大的Python数据科学技巧

太香了!Python 优化提速的 8 个小技巧!

点击 阅读原文了解更多

最新文章

随机文章