因为time.sleep()是同步阻塞调用,会冻结事件循环线程,导致所有协程暂停调度;正确做法是用await asyncio.sleep()让出控制权,或对requests等同步I/O用run_in_executor移交线程池执行。

为什么直接在 async 函数里调用 time.sleep() 会卡住整个服务
因为 time.sleep() 是同步阻塞调用,它会让当前线程停住指定秒数,而 asyncio 的事件循环就跑在这个线程上。一旦卡住,所有其他协程都得等——不是“慢”,而是彻底暂停调度。你发 10 个请求,串行执行,耗时接近总和;而不是并发执行,耗时接近单次最大值。
loop.run_in_executor 的实际执行路径是什么
它不 magic,只是把同步函数扔进后台线程池执行:
-
loop.run_in_executor(None, func, *args)中的None表示用默认ThreadPoolExecutor - 事件循环立刻返回一个
Future,继续调度别的协程 - 线程池里的空闲线程拿到
func和参数,真正执行它 - 线程执行完后,结果被设回
Future,await 才拿到返回值
requests.get() 必须 wrap 进 run_in_executor 吗
是的,除非你换用 aiohttp 这类原生异步库。原因很实在:
图片提示词生成器?不止如此。 马甲系统 —— 把脑海中的画面,翻译成AI能理解的专业表达。 用得越多,它越懂你:首次需要多问几句确认方向,用久了几乎一说就懂。 用得越多,它越快:缓存机制让后续对话越来越省。 RAG进化:成功案例持续入库,越跑越聪明。 输入「新手指南」查看完整功能介绍
-
requests底层用的是urllib3+ 同步 socket,所有读写都阻塞 - 哪怕只发一个请求,
await requests.get(...)会报错——requests根本不支持 await - 常见错误现象:日志没异常,QPS 掉到 1/10,监控显示 CPU 低但响应延迟飙升
- 正确写法是:
await loop.run_in_executor(None, requests.get, url)
什么时候不该用 run_in_executor
不是所有同步代码都值得扔进线程池——调度本身有开销:
立即学习“Python免费学习笔记(深入)”;
- 小量字符串处理(如
json.dumps()小对象)、字典遍历、简单计算,直接写更高效 - CPU 密集型任务(如大数组排序、加密)建议用
ProcessPoolExecutor,避免 GIL 拖累 - 注意异常传播:线程里抛的异常,会在 await 处重新 raise,别漏掉 try/except
- 别传 lambda 或闭包里带非序列化对象(如文件句柄、数据库连接),线程间不共享内存
min(32, os.cpu_count() + 4),高并发场景下可能成为瓶颈;需要压测后按需调大 max_workers,而不是无脑堆线程。

















