Python异步编程
Python 异步编程的核心是 asyncio 事件循环 + async/await 协程,解决 I/O 密集型任务的并发等待问题。同步代码里等待网络响应、读写文件时 CPU 是空闲的;协程在遇到 await 时主动让出控制权,让事件循环去执行其他任务,从而用单线程吃掉大量 I/O 并发。但协程对 CPU 密集型任务无效——那要靠 multiprocessing 多进程绕过 GIL。
协程与事件循环
async def 定义的函数调用时不会立即执行,而是返回一个协程对象,只有交给事件循环才会运行。await 是让出点:遇到它,当前协程挂起,事件循环调度其他就绪协程。
import asyncio
async def fetch(url):
print(f"开始 {url}")
await asyncio.sleep(1) # 模拟网络等待,让出控制权
print(f"完成 {url}")
return url
async def main():
# 并发执行三个任务,总耗时 ≈ 1 秒而非 3 秒
results = await asyncio.gather(
fetch("a.com"),
fetch("b.com"),
fetch("c.com"),
)
print(results)
asyncio.run(main())asyncio.run() 是入口(Python 3.7+);gather 并发收集结果,create_task 创建后台任务,asyncio.sleep 模拟 I/O 等待。真实场景中配合 aiohttp(HTTP)、asyncpg(PostgreSQL)等异步库使用。
同步代码 vs 协程
| 维度 | 同步 | 协程 |
|---|---|---|
| 执行方式 | 串行阻塞 | 单线程并发调度 |
| 等待 I/O | CPU 空转 | 让出控制权给其他任务 |
| 适合场景 | CPU 密集、简单脚本 | 高并发 I/O(网络、爬虫、API 服务) |
| 代码风格 | 顺序直观 | async/await 标注 |
什么时候值得用 asyncio
只有同时满足"大量并发 I/O 等待"和"单次等待耗时可观"时才值得——比如爬 1000 个网页。只有一两个请求、或主要做 CPU 计算时,协程只会增加复杂度。
GIL 与多进程
GIL(Global Interpreter Lock,全局解释器锁)保证同一时刻只有一个线程执行 Python 字节码。它简化了 CPython 的内存管理,代价是多线程无法利用多核 CPU 做并行计算——所以:
- I/O 密集:多线程或协程都能提速(等待时释放 GIL)
- CPU 密集:多线程无效,用
multiprocessing多进程(每进程独立解释器,各占一个核)
from multiprocessing import Pool
def cpu_task(n):
return sum(i * i for i in range(n))
with Pool(4) as pool: # 4 个进程并行
results = pool.map(cpu_task, [10**6] * 8)与 Java 的线程模型对比:Java 多线程能并行利用多核,Python 靠多进程;Java 的 CompletableFuture 对应 Python 的 asyncio,两者都是异步非阻塞思路。JavaScript 的事件循环与协程同为单线程并发模型,见 异步编程 的三代演进对比。