协程切换在用户态完成,避免内核态开销,内存占用低且无锁竞争,但要求全链路异步、规避CPU密集操作;混用同步代码或错误共享状态将导致性能劣化甚至阻塞。

协程切换不进内核,线程切换必须进内核
协程挂起(await)只保存局部变量和执行位置,由 asyncio 事件循环在用户态直接跳转到下一个就绪协程;而线程调用 socket.recv() 或 time.sleep() 会立刻陷入内核态等待,操作系统必须保存寄存器、栈指针、页表等完整上下文——这个过程耗时从数百纳秒到微秒级,且随线程数增长非线性飙升。
实操建议:
- 用
ps -o pid,vsz,comm -p $(pidof python)对比内存占用:1000 个并发请求,aiohttp常驻内存约 20–30MB;threading+requests往往超 200MB,甚至因默认线程栈 8MB × 1000 触发 OOM - 别在协程里写
time.sleep(1),它会阻塞整个事件循环;必须用await asyncio.sleep(1)
协程天然规避锁竞争,但共享状态仍需显式建模
协程运行在单线程中,任意时刻只有一个在执行,counter += 1 这类操作只要不被 await 中断就是原子的;多线程则必须加 threading.Lock,否则读-改-写三步必然交错出错。
但注意:global counter; counter += 1 在协程里看似没报错,其实是竞态灾难——多个协程按任意顺序执行该行,结果不可预测。
立即学习“Python免费学习笔记(深入)”;
实操建议:
- 跨 Task 计数或缓存,用
asyncio.Lock包裹临界区,别碰threading.Lock(它会阻塞整个事件循环) - 复杂状态逻辑优先走
asyncio.Queue,生产者-消费者模型比锁更清晰、更易测 - 所有
await点都是潜在的状态让渡点,依赖执行顺序的逻辑必须显式建模(比如用asyncio.Event或信号量)
协程要求所有 I/O 都是异步的,误用同步函数等于自废武功
await 后面的函数必须是真正异步的,如 aiohttp.ClientSession.get();若误用 requests.get() 这类同步函数,会直接阻塞整个事件循环,1000 个协程全卡住,效果等同于单线程串行。
常见错误现象:
- HTTP 请求响应极慢,CPU 占用却很低
- 用
curl -v测接口延迟正常,但服务端日志显示请求堆积、无并发 -
asyncio.run()启动后,程序看起来“活着”,但不再处理新请求
实操建议:
- HTTP 客户端只用
aiohttp、httpx.AsyncClient,禁用requests - 数据库访问用
asyncpg、aiomysql、motor(MongoDB),不用psycopg2或pymysql - 文件读写慎用
asyncio.to_thread()包装open(),高频小文件建议预加载或换用内存映射
asyncio.run() 启动的是单线程事件循环,它不解决 CPU 瓶颈
asyncio.run() 创建的默认事件循环绑定在主线程,无法利用多核 CPU。很多人误以为“异步=多核并行”,其实 asyncio 只优化 I/O 等待效率,不解决 CPU 计算瓶颈。
常见错误现象:
- 协程里写
sum(range(10**7))或正则匹配大文本,整个事件循环卡死,其他所有协程冻结 - 接口平均响应时间突增,监控显示 CPU 使用率不高但延迟毛刺严重
实操建议:
- CPU 密集型任务(如解析、加密、格式转换)必须交给
loop.run_in_executor()托管到线程池 - 纯计算场景(如图像处理、科学计算)应直接上
multiprocessing,别硬套协程 - 混合场景(请求数据 + 本地解析)采用“协程主流程 +
run_in_executor托管计算”的分层结构
async def,而是把整个 I/O 生态链都换成异步实现,并理解每个 await 点背后的调度语义。


















