把十几个同步请求丢进 run_in_executor,前几个还正常,后面全部排队,本该两秒返回的接口等了四十多秒,CPU 却几乎是空的。日志里能对上时间点,这种反而好排。
不指定执行器时用的是循环自带的默认线程池,线程数按 CPU 核数算,四个核就只有四个线程,慢任务一多全堵在队列里干等。这一点先确认清楚,后面就不用反复试。
pool = ThreadPoolExecutor(max_workers=40) loop = asyncio.get_running_loop() res = await loop.run_in_executor(pool, requests.get, url) pool.shutdown(wait=True)
围绕「默认线程池占满」排查时,命令行跑一遍比在面板里点十次有用,面板会替你做掉不少隐式操作。线上出问题先对时间点,跟发布、备份、任务执行的时刻排一排,能省掉一半功夫。
日志级别临时调高可以,记得改回去,长期开着既占磁盘又掩盖真问题。
处理上按这个顺序:创建专用的 ThreadPoolExecutor,按 IO 等待时间给足线程数,别用默认池;把 executor 显式传给 run_in_executor,线程数在创建时就定好,别依赖默认那个;任务结束后调 shutdown 并等待完成,别让工作线程一直悬着。
结果对不对很好判断,批量任务总耗时降到接近最慢那一单,CPU 空转的情况不再出现。
