Sanic服务器:await含循环函数时请求无法并发的解决咨询
解决Sanic中CPU密集型for循环导致请求无法并发的问题
我明白你的困扰——你以为给encode函数加了await就能让它异步不阻塞,但实际上你的for循环是纯CPU密集的同步代码,await根本帮不上忙,Sanic的单线程事件循环会被这个循环死死占住,直到它执行完才能处理下一个请求。
核心原因拆解
异步框架的事件循环靠的是任务主动交出控制权来实现并发,比如IO操作等待时会把控制权还给循环。但纯CPU密集的同步代码(比如你的for循环)不会有任何交出控制权的动作,事件循环只能等着它跑完,自然没法处理其他请求。
正确解决方案:把CPU任务移到执行器中
我们需要把CPU密集型任务放到线程池或进程池里执行,这样事件循环可以暂时放下这个任务,去处理其他请求,等执行池里的任务完成后再回来处理结果。
基础实现(线程池)
假设你的原代码是这样的:
from sanic import Sanic, response app = Sanic("TestApp") def encode(): # 模拟CPU密集型业务操作 result = 0 for i in range(10**8): result += i return result @app.route("/") async def test(request): # 这里加await也没用,因为encode是同步函数 res = await encode() return response.text(f"Result: {res}") if __name__ == "__main__": app.run(host="0.0.0.0", port=8000)
改成异步兼容版本,用loop.run_in_executor把同步任务包装成可await的协程:
from sanic import Sanic, response import asyncio app = Sanic("TestApp") def encode(): # 保留原有的CPU密集型for循环 result = 0 for i in range(10**8): result += i return result @app.route("/") async def test(request): # 获取当前事件循环 loop = asyncio.get_event_loop() # 把同步任务提交到默认线程池执行,返回可await的对象 res = await loop.run_in_executor(None, encode) return response.text(f"Result: {res}") if __name__ == "__main__": app.run(host="0.0.0.0", port=8000)
进阶优化(进程池)
如果你的CPU任务非常重,受限于Python的GIL,线程池的提升有限,可以自定义进程池避开GIL限制:
from concurrent.futures import ProcessPoolExecutor from sanic import Sanic, response import asyncio app = Sanic("TestApp") # 创建进程池,数量建议和CPU核心数一致 executor = ProcessPoolExecutor(max_workers=4) def encode(): result = 0 for i in range(10**8): result += i return result @app.route("/") async def test(request): loop = asyncio.get_event_loop() # 提交到自定义进程池执行 res = await loop.run_in_executor(executor, encode) return response.text(f"Result: {res}") if __name__ == "__main__": app.run(host="0.0.0.0", port=8000)
验证效果
现在你再在两个控制台同时执行time curl http://0.0.0.0:8000/,会发现两个请求的耗时基本一致,而不是串行等待(第一个执行完第二个才开始),说明请求已经可以并发处理了。
内容的提问来源于stack exchange,提问作者Jason
相关产品推荐
相关产品推荐

