mac python multithreading io tornado

tornado python mac



Tornado con ThreadPoolExecutor (0)

Tengo configuración que utiliza Tornado como servidor http y marco http personalizado. Idea es tener un único controlador de tornados y cada solicitud que llegue debería enviarse a ThreadPoolExecutor y dejar Tornado para escuchar nuevas solicitudes. Una vez que el hilo termina de procesar la solicitud, se llama a la devolución de llamada que envía una respuesta al cliente en el mismo subproceso donde se ejecuta el bucle IO.

Desnudo, el código se ve algo como esto. Clase de servidor http base:

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()

Implementación respaldada por Tornado de 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()

Manejador principal que maneja todas las solicitudes de tornado (implementado solo GET, pero el resto sería el mismo):

class MainHandler(): def initialize(self, submit, router): self.submit = submit self.router = router def worker(self, request): responder, kwargs = self.router.resolve(request) response = responder(**kwargs) return res def on_response(self, response): # when this is called response should already have result if isinstance(response, Future): response = response.result() # response is my own class, just write returned content to client self.write(response.data) self.flush() self.finish() def _on_response_ready(self, response): # schedule response processing in ioloop, to be on ioloop thread ioloop.IOLoop.current().add_callback( partial(self.on_response, response) ) @web.asynchronous def get(self, url): self.submit( self._on_response_ready, # callback url=url, method=''post'', original_request=self.request )

El servidor se inicia con algo como:

router = Router() server = TornadoServer(router, 1111, max_workers=50) server.run()

Entonces, como puede ver, el manejador principal solo envía cada solicitud al grupo de subprocesos y cuando se realiza el proceso, se llama a la devolución de llamada ( _on_response_ready ) que simplemente planifica el final de la solicitud para ejecutarse en el bucle IO (para asegurarse de que se realice en el mismo hilo) donde se está ejecutando IO loop).

Esto funciona. Al menos parece que sí.

Mi problema aquí es el rendimiento con respecto al número máximo de trabajadores en ThreadPoolExecutor.

Todos los manejadores están vinculados IO, no hay cálculos en curso (en su mayoría están esperando DB o servicios externos), así que con 50 trabajadores esperaría que 50 solicitudes concurent terminen aproximadamente 50 veces más rápido que 50 solicitudes concurent con un solo trabajador.

Pero ese no es el caso. Lo que veo son solicitudes casi idénticas por segundo cuando tengo 50 trabajadores en el pool de hilos y 1 trabajador.

Para medir, he usado Apache-Bench con algo como:

ab -n 100 -c 10 http://localhost:1111/some_url

¿Alguien tiene idea de qué estoy haciendo mal? ¿Entendí mal cómo funcionan Tornado o ThreadPool? ¿O combinación?