Python 带线程池执行器的Tornado

Python 带线程池执行器的Tornado,python,multithreading,io,tornado,Python,Multithreading,Io,Tornado,我有一个使用Tornado作为http服务器和定制http框架的设置。想法是使用单个tornado处理程序,每个到达的请求都应该提交给ThreadPoolExecutor,然后离开tornado,以侦听新的请求。一旦线程完成对请求的处理,就会调用回调函数,将响应发送到执行IO循环的同一线程中的客户端 简而言之,代码看起来是这样的。基本http服务器类: class HttpServer(): def __init__(self, router, port, max_workers):

我有一个使用
Tornado
作为http服务器和定制http框架的设置。想法是使用单个tornado处理程序,每个到达的请求都应该提交给
ThreadPoolExecutor
,然后离开
tornado
,以侦听新的请求。一旦线程完成对请求的处理,就会调用回调函数,将响应发送到执行IO循环的同一线程中的客户端

简而言之,代码看起来是这样的。基本http服务器类:

class HttpServer():
    def __init__(self, router, port, max_workers):
        self.router = router
        self.port = port
        self.max_workers = max_workers

    def run(self):
        raise NotImplementedError()
Tornado支持的HttpServer实施:

class TornadoServer(HttpServer):
    def run(self):
        executor = futures.ThreadPoolExecutor(max_workers=self.max_workers)

        def submit(callback, **kwargs):
            future = executor.submit(Request(**kwargs))
            future.add_done_callback(callback)
            return future

        application = web.Application([
            (r'(.*)', MainHandler, {
                'submit': submit,
                'router': self.router   
            })
        ])

        application.listen(self.port)

        ioloop.IOLoop.instance().start()
处理所有tornado请求的主处理程序(仅实现GET,但其他将相同):

服务器是以如下方式启动的:

router = Router()
server = TornadoServer(router, 1111, max_workers=50)
server.run()
ab -n 100 -c 10 http://localhost:1111/some_url
所以,正如您所看到的,主处理程序只是将每个请求提交到线程池,当处理完成时,调用回调(
\u on\u response\u ready
),它只是安排请求完成在IO循环上执行(以确保它在执行IO循环的同一线程上完成)

这很有效。至少看起来是这样

我这里的问题是ThreadPoolExecutor中最大工作线程的性能

所有处理程序都是IO绑定的,没有进行计算(它们大多在等待DB或外部服务),因此对于50个工作者,我预计50个concurent请求的完成速度将比只有一个工作者的50个concurent请求快大约50倍

但事实并非如此。当线程池中有50个工作线程和1个工作线程时,我每秒看到的请求几乎相同

为了进行测量,我使用了Apache Bench,其中包括:

router = Router()
server = TornadoServer(router, 1111, max_workers=50)
server.run()
ab -n 100 -c 10 http://localhost:1111/some_url

有人知道我做错了什么吗?我是否误解了Tornado或ThreadPool的工作原理?还是组合?

正如kwarunek所建议的,postgres的momoko包装解决了这个问题。如果您想从外部合作者那里获得进一步的调试建议,那么在每次访问DB之前发布测试任务的带有时间戳的调试日志(10)会有所帮助。

我觉得这段代码或多或少是正确的。50名员工到底是如何进行I/O的?你看到了多少QP?IOLoop线程中的HTTP处理是否会成为瓶颈?@BenDarnell,workers中的I/O主要是查询数据库和调用外部服务。IOLoop中的HTTP处理非常简单(测量表明,它比实际处理花费的时间要少得多(1%或2%)。如果QPS表示每秒查询,其中查询为DB查询,则每个HTTP请求执行一个或两个查询。您用于数据库查询的库是什么?是否有一个C库在阻止I/O时未能释放GIL?我正在使用SqlAlchemy与Postgres和psycopg2…我在文档中找不到与ps相关的任何地方ycogp2或SqlAlchemy可以做到这一点,所以我没有想到。我刚刚用pg8000(纯python postgres驱动程序)进行了测试,我得到了相同的行为。我使用tornado+momoko,一个线程,一个进程,我的瓶颈是池大小和db时间,而不是tornado