Python 在 Web 领域长期受到 GIL(全局解释器锁)的限制。为了在单进程下榨干高并发 IO 性能,从 Python 3.4 的 asyncio 模块到 3.5 引入的 async/await 原生语法糖,Python 的异步并发能力进入了崭新的时代。
1. 协程的历史根源:生成器与 yield from
在底层,Python 协程之所以能够在函数执行中途“暂停”并交出 CPU 控制权,是基于经典迭代器协议中的 yield 操作:
send(value): 向挂起的生成器传值并恢复其执行。throw(): 在挂起处引发异常。yield from: 自动建立双向通道,将子生成器的产出和异常直接传递给调用方。
2. 事件循环(Event Loop)与 Future/Task
事件循环本质是一个死循环,底层通过 Linux 的 epoll(或 macOS 的 kqueue)监听所有已注册的非阻塞 Socket 描述符:
import asyncio
async def fetch_api(url):
print(f"Connecting to {url}...")
await asyncio.sleep(1) # 非阻塞休眠,把控制权交还事件循环
return f"Response from {url}"
async def main():
# 并发调度两个任务
task1 = asyncio.create_task(fetch_api("https://api.github.com"))
task2 = asyncio.create_task(fetch_api("https://api.v2ex.com"))
results = await asyncio.gather(task1, task2)
print("All tasks finished:", results)
asyncio.run(main())
3. 避坑指南:阻塞调用切忌放入异步主线程
黄金法则:在 async 函数中,绝不能执行任何阻塞性调用(如 time.sleep()、传统的 requests.get() 或密集 CPU 计算)。 一旦阻塞,整个事件循环将彻底卡死,所有后续请求全部超时。
对于无法避免的 CPU 密集型任务或第三方同步库,应交由线程池执行:
loop = asyncio.get_running_loop()
# 将同步阻塞操作转交给默认 ThreadPoolExecutor 运行
result = await loop.run_in_executor(None, sync_heavy_task, arg1)