[Python3高阶编程] - 异步编程深度学习指南一: 基础知识
·
下面学习的内容涉及源代码: https://blog.csdn.net/andylin02/article/details/159652101?spm=1001.2014.3001.5502
一、为什么需要异步?——从阻塞 I/O 到并发模型
1.1 同步 vs 异步 I/O
- 同步 I/O:发起请求 → 线程阻塞 → 等待完成 → 继续执行
(如requests.get()) - 异步 I/O:发起请求 → 立即返回 → 执行其他任务 → 数据就绪时回调处理
(如aiohttp.get())
1.2 异步的优势
- 高并发:单线程可同时处理数千个 I/O 操作(如 Web 请求、数据库查询)
- 低资源消耗:无需创建大量线程(避免上下文切换开销)
- 适合 I/O 密集型场景:网络、文件、数据库等
⚠️ 注意:异步不能加速 CPU 密集型任务(此时用多进程)
二、核心概念解析
2.1 协程(Coroutine)
- 定义:可暂停和恢复的函数
- 声明方式:
async def fetch(url): return await aiohttp.get(url) # 这里只是示例 - 调用
fetch("...")返回一个 协程对象(未执行!)
2.2 事件循环(Event Loop)
- 作用:调度协程、监听 I/O 事件、执行回调
- 获取当前 loop:
loop = asyncio.get_event_loop() # 或 (Python 3.7+) loop = asyncio.get_running_loop()
2.3 Future / Task
| 对象 | 说明 |
|---|---|
Future | 表示“未来某个时刻会完成的操作”(底层抽象) |
Task | 对协程的封装,自动调度执行(推荐使用) |
task = asyncio.create_task(fetch("https://example.com"))
result = await task
三、async/await 语法详解
3.1 基本结构
import asyncio
async def main():
print("Start")
await asyncio.sleep(1) # 模拟异步 I/O
print("End")
# 启动(Python 3.7+)
asyncio.run(main())
3.2 关键规则
await只能在async def中使用await后必须是 awaitable 对象(协程、Task、Future)- 不要在异步函数中调用阻塞函数(如
time.sleep,requests.get)
❌ 错误示例:
async def bad():
time.sleep(2) # 阻塞整个事件循环!
✅ 正确做法:
async def good():
await asyncio.sleep(2) # 非阻塞
四、并发执行模式
4.1 顺序执行(无并发)
async def main():
await fetch(url1)
await fetch(url2) # 等第一个完成后才开始
4.2 并发执行(推荐)
方式1:asyncio.gather()
async def main():
results = await asyncio.gather(
fetch(url1),
fetch(url2),
fetch(url3)
)
- 特点:所有任务同时启动,等待全部完成
- 异常处理:默认一个失败则全部取消(可用
return_exceptions=True)
方式2:asyncio.create_task() + await
async def main():
t1 = asyncio.create_task(fetch(url1))
t2 = asyncio.create_task(fetch(url2))
r1 = await t1
r2 = await t2
- 特点:更灵活,可分批等待
方式3:限制并发数(防爆)
from asyncio import Semaphore
sem = Semaphore(10) # 最多10个并发
async def safe_fetch(url):
async with sem:
return await fetch(url)
# 提交1000个任务
tasks = [safe_fetch(url) for url in urls]
results = await asyncio.gather(*tasks)
五、异步上下文管理器与迭代器
5.1 异步上下文管理器(async with)
用于异步资源管理(如数据库连接、HTTP 会话):
async def fetch_data():
async with aiohttp.ClientSession() as session:
async with session.get("https://api.example.com") as resp:
return await resp.json()
5.2 异步迭代器(async for)
用于异步生成数据流:
async def async_range(n):
for i in range(n):
await asyncio.sleep(0.1)
yield i
async def main():
async for x in async_range(5):
print(x)
六、与同步代码集成
6.1 在异步中运行阻塞函数
使用 loop.run_in_executor() 将阻塞任务交给线程池:
def blocking_io():
with open('/tmp/file.txt', 'rb') as f:
return f.read()
async def main():
loop = asyncio.get_running_loop()
data = await loop.run_in_executor(None, blocking_io)
print(data)
💡
None表示使用默认线程池(ThreadPoolExecutor)
6.2 从同步代码调用异步函数
# 不推荐(仅用于顶层)
asyncio.run(async_func())
# 或在已有 loop 中(如 Jupyter)
await async_func()
⚠️ 不要混用
asyncio.run()多次(会创建多个 loop)
七、常用异步库推荐
| 领域 | 库 | 说明 |
|---|---|---|
| HTTP 客户端 | aiohttp | 支持客户端和服务端 |
| HTTP 客户端 | httpx | 更现代,支持 HTTP/2 |
| 数据库 | asyncpg (PostgreSQL) | 高性能 |
| 数据库 | aiomysql / aiosqlite | MySQL / SQLite |
| Web 框架 | FastAPI | 基于 Starlette,原生异步 |
| Web 框架 | Quart | Flask 的异步版 |
| 爬虫 | scrapy (via scrapy-poet) | 或自研 aiohttp + asyncio |
八、调试与测试
8.1 调试技巧
- 启用调试模式:
asyncio.run(main(), debug=True)
- 查看未完成的 Task:
tasks = asyncio.all_tasks()
for t in tasks:
print(t)
8.2 单元测试
使用 pytest-asyncio:
import pytest
@pytest.mark.asyncio
async def test_fetch():
result = await fetch("https://example.com")
assert result is not None
九、常见陷阱与最佳实践
❌ 陷阱1:忘记 await
async def main():
fetch(url) # 协程对象被创建但未执行!
✅ 正确:await fetch(url)
❌ 陷阱2:在异步函数中使用阻塞 I/O
async def handler():
requests.get(url) # 阻塞整个事件循环!
✅ 正确:用 aiohttp 或 run_in_executor
❌ 陷阱3:滥用 asyncio.run()
- 在已有事件循环中(如 Jupyter、Web 框架)不要调用
asyncio.run() - 改为直接
await
✅ 最佳实践
- I/O 操作必须异步化(网络、文件、DB)
- CPU 密集型任务交给
ProcessPoolExecutor - 限制并发数(防止打爆目标服务器或本地资源)
- 使用类型注解(
async def f() -> str:) - 优雅关闭资源(
async with)
十、底层原理简析(进阶)
10.1 事件循环如何工作?
- 基于 Reactor 模式
- 使用系统调用(如 Linux 的
epoll、macOS 的kqueue)监听 I/O 事件 - 当 socket 可读/可写时,回调对应协程继续执行
10.2 协程 vs 线程
| 特性 | 协程 | 线程 |
|---|---|---|
| 切换开销 | 极低(用户态) | 高(内核态) |
| 内存占用 | KB 级 | MB 级 |
| 并发能力 | 数万+ | 数百(受 GIL 和内存限制) |
| 编程复杂度 | 需理解异步模型 | 需处理锁和竞态 |
十一、学习路径建议
- 入门:掌握
async/await、asyncio.run()、gather - 实战:写一个异步爬虫或 API 客户端
- 进阶:理解事件循环、Task 调度、异常传播
- 精通:阅读
asyncio源码、实现自定义异步协议
十二、推荐阅读
- 官方文档:https://docs.python.org/3/library/asyncio.html
- 书籍:《Python 异步编程实战》(中文)、《Using Asyncio in Python》(Caleb Hattingh)
- 视频:David Beazley 的 “Async IO in Python” 演讲(YouTube)
✅ 总结
异步编程不是银弹,而是解决 I/O 并发问题的利器。
- 用对场景(I/O 密集型)→ 性能飞跃
- 用错场景(CPU 密集型)→ 得不偿失
- 忽略规则(阻塞调用、忘记 await)→ 程序退化为同步
掌握 async/await,你就能用 Python 写出媲美 Node.js、Go 的高并发服务!
📌 动手建议:
立即尝试将一个同步爬虫改造成异步版本,对比 QPS 提升效果!
更多推荐
所有评论(0)