如何在Tornado中使用多进程处理阻塞调用以避免主线程阻塞
在Tornado中异步执行阻塞调用,避免主线程阻塞
你遇到的这个场景太典型了——Tornado作为单线程异步Web框架,一旦主线程被阻塞,整个服务就会卡住,所以必须把这类耗时的阻塞操作放到独立的线程或者进程里跑。下面给你两种实用的实现方案,直接就能套到你的代码里:
方案一:用线程池处理IO密集型阻塞操作
如果你的blocking_get是IO密集型(比如调用外部API、读写数据库这类),用线程池就足够了,开销比进程小很多。Tornado可以很好地和concurrent.futures.ThreadPoolExecutor配合:
import tornado.web import tornado.gen import time from tornado.ioloop import IOLoop from concurrent.futures import ThreadPoolExecutor # 全局线程池,根据你的服务规模调整线程数 thread_pool = ThreadPoolExecutor(max_workers=4) ## 在独立线程中运行此函数 def blocking_get(var1): print("blocking function started") time.sleep(2) # 模拟阻塞操作 return {"res": f"result for {var1}"} class RootHandler(tornado.web.RequestHandler): # Tornado 5+ 推荐用 async/await 写法 async def get(self): var1 = self.get_argument("var1", default="default") # 把阻塞函数提交到线程池,await获取结果 result = await IOLoop.current().run_in_executor(thread_pool, blocking_get, var1) self.write(result) if __name__ == "__main__": app = tornado.web.Application([ (r"/", RootHandler), ]) app.listen(8888) print("Server started on http://localhost:8888") IOLoop.current().start()
这里的核心是IOLoop.current().run_in_executor(),它会把阻塞函数丢到指定的线程池里执行,主线程可以继续处理其他请求,等线程池里的任务完成后再返回结果。
方案二:用进程池处理CPU密集型阻塞操作
如果你的阻塞操作是CPU密集型(比如大量计算),那线程池受GIL限制效率不高,这时候就得用进程池:
import tornado.web import tornado.gen import time from tornado.ioloop import IOLoop from concurrent.futures import ProcessPoolExecutor # 全局进程池,进程数建议和CPU核心数匹配 process_pool = ProcessPoolExecutor(max_workers=2) ## 在独立进程中运行此函数 def blocking_get(var1): print("blocking function started in process") time.sleep(2) # 模拟CPU密集型操作(实际可能是复杂计算) return {"res": f"result for {var1}"} class RootHandler(tornado.web.RequestHandler): async def get(self): var1 = self.get_argument("var1", default="default") # 提交到进程池执行 result = await IOLoop.current().run_in_executor(process_pool, blocking_get, var1) self.write(result) if __name__ == "__main__": app = tornado.web.Application([ (r"/", RootHandler), ]) app.listen(8888) print("Server started on http://localhost:8888") IOLoop.current().start()
注意:进程之间的参数和返回值必须是可序列化的(比如字典、字符串、数字这些),如果要传递复杂对象,得确保它们能被pickle序列化。
额外提示
- 线程池/进程池的大小要根据你的服务负载调整,线程池适合IO密集,进程池适合CPU密集,别盲目开太多,不然会导致资源耗尽。
- 如果你的Tornado版本低于5.0,那得用
@tornado.gen.coroutine装饰器,配合yield来替代async/await,写法大概是这样:
class RootHandler(tornado.web.RequestHandler): @tornado.gen.coroutine def get(self): var1 = self.get_argument("var1", default="default") result = yield IOLoop.current().run_in_executor(thread_pool, blocking_get, var1) self.write(result)
这样处理后,你的主线程就永远不会被阻塞,多个请求可以并行处理啦!
内容的提问来源于stack exchange,提问作者aja
相关产品推荐
相关产品推荐

