Вы можете использовать ThreadPoolExecutor
или ProcessPoolExecutor
для запуска трудоемкого кода в отдельных потоках / процессах:
import math
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
import tornado.ioloop
import tornado.web
def blocking_task(number):
return len(str(math.factorial(number)))
class MainHandler(tornado.web.RequestHandler):
executor = ProcessPoolExecutor(max_workers=4)
# executor = ThreadPoolExecutor(max_workers=4)
async def get(self):
number = 54545 # factorial calculation takes about one second on my machine
# result = blocking_task(number) # use this line for classic (non-pool) function call
result = await tornado.ioloop.IOLoop.current().run_in_executor(self.executor, blocking_task, number)
self.write("result has %d digits" % result)
def make_app():
return tornado.web.Application([
(r"/", MainHandler),
])
if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
Я использовал простое вычисление factorial
для моделирования нагрузки на процессор Задача и тестирование выше, используя wrk :
wrk -t2 -c4 -d30s http://127.0.0.1:8888/
Running 30s test @ http://127.0.0.1:8888/
2 threads and 4 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 1.25s 34.16ms 1.37s 72.04%
Req/Sec 2.54 3.40 10.00 83.75%
93 requests in 30.04s, 19.89KB read
Requests/sec: 3.10
Transfer/sec: 678.00B
Без исполнителя я бы получал около 1 запросов / сек; конечно, вам нужно настроить max_workers
в соответствии с вашими настройками.
Если вы собираетесь тестировать с помощью браузера, помните о возможных ограничениях .
Редактировать
Я изменил код так, чтобы он позволял исполнителю процесса вместо исполнителя потока, но я сомневаюсь, что в вашем случае это будет иметь большое значение, главным образом потому, что вызовы argon2 должны освободить GIL, но вы все равно должны его протестировать .