一、理解Python协程的核心概念
什么是协程、以及它与线程的区别
协程是一种用于实现异步编程的控制流结构,它比线程更轻量,切换成本更低,因此在处理大量并发任务时更高效。与传统的线程相比,协程不需要操作系统级别的上下文切换,从而减少了开销。
在Python中,事件循环负责调度所有协程的执行,将就绪的任务推入执行队列,等待阻塞的操作返回后再继续。这种模式让你的程序在单线程中实现并发,以对外部 IO 请求的等待时间槽进行重用。
需要牢记的是,协程是为了非阻塞 IO 而设计,它们通常与异步 I/O、网络请求、磁盘 IO 等场景契合。阻塞操作会破坏协程的并发性,因此要用非阻塞版本的 API 或将阻塞逻辑放到专门的线程池中执行。
二、async/await的基础语法与执行流程
语法要点与执行机制
在 Python 中,async def 用于定义一个协程函数,调用它不会立即执行,而是返回一个协程对象。要真正执行,需要在事件循环中调度它,并用 await 关键字挂起当前协程,等待另一个协程完成后再继续执行。
通过将异步函数组合成任务实现并发,asyncio 提供了事件循环与调度器,能让你以直观的写法表达并发逻辑,而无需显式创建线程。
下面的代码演示了最小的异步执行流程:首先定义协程,然后在事件循环中运行入口协程。asyncio.run 是常用的进入点,简化了事件循环的创建与销毁。
import asyncioasync def hello():print("Hello")await asyncio.sleep(1)print("World")async def main():await hello()# 启动事件循环并执行 main
asyncio.run(main())await 的核心在于“等待另一个异步操作完成再继续”,这使得你可以把一个复杂的工作流拆成若干协程,按需并发执行。
如果你不是从顶层入口启动,可以使用事件循环来调度:loop = asyncio.get_event_loop(),然后调用 loop.run_until_complete(main()),最后关闭循环。
三、实战场景应用:并发网络请求与IO密集型任务
常见场景与设计思路
在网络请求、数据库查询、文件 I/O 等 IO 密集型场景中,协程可以显著提升并发吞吐量,因为只要一个请求在等待响应,事件循环就可以调度其他任务继续工作。
设计思路通常是把 I/O 操作封装成 独立的异步函数,再用 asyncio.gather、wait、或 as_completed 等工具进行并发执行与结果聚合。
需要注意的是,外部库要提供异步 API,否则就需要在其他线程或进程中执行阻塞调用,使用 run_in_executor 将阻塞工作转为异步友好的任务。
import asyncio
import aiohttpasync def fetch(url, session):async with session.get(url) as resp:text = await resp.text()return url, len(text)async def main(urls):async with aiohttp.ClientSession() as session:tasks = [fetch(u, session) for u in urls]results = await asyncio.gather(*tasks)for u, size in results:print(f"{u} -> {size} chars")urls = ["https://example.com","https://httpbin.org/get","https://www.python.org",
]
asyncio.run(main(urls))四、常见陷阱、调试与性能优化
处理阻塞、混合同步调用、锁和队列
阻塞操作会暂停事件循环,因此要避免在异步上下文中执行耗时的 CPU 任务,必要时使用 run_in_executor 将 CPU 密集型工作放入线程池或进程池执行。
如果在同一个程序中同时使用 同步代码 与 异步代码,要清晰划分边界,尽量避免在事件循环中直接调用阻塞的 I/O。你可以通过 asyncio.Lock、asyncio.Queue 等原生同步原语实现协程间的安全协作。
调试时可以开启事件循环调试模式与跟踪,帮助定位协程的挂起点、任务的创建与完成情况;同时,必要时用 print、日志记录、或专业调试工具来观察任务的执行顺序。
五、实战案例:并发下载多个网页
任务划分与并发执行
本案例展示如何用 asyncio.gather 同时发起多个网络请求,并在全部完成后汇总结果。核心思想是将每个下载封装成一个独立的协程,然后并发执行以最大化吞吐量。

通过将网络请求抽象为可复用的异步函数,可以在需要时方便扩展并发任务数量,达到对 IO 的高效利用。
下面的示例实现了对三个网页的并发请求,输出每个网页的长度信息,帮助你理解并发执行的实际效益。
import asyncio
import aiohttpasync def fetch_size(session, url):async with session.get(url) as resp:content = await resp.read()return url, len(content)async def main():urls = ["https://example.com","https://httpbin.org/get","https://www.python.org",]async with aiohttp.ClientSession() as session:tasks = [fetch_size(session, u) for u in urls]results = await asyncio.gather(*tasks)for url, size in results:print(f"{url} 內容长度: {size} 字节")asyncio.run(main())通过以上实现,你可以看到并发下载的吞吐量明显提升,尤其是在面对大量小型请求或慢响应时,协程的优势更加明显。


