[Optimize the three options for tornado blocking tasks]
1. Optimize blocked tasks to make them run faster. It is often caused by a slow dB query or a complicated upper-layer template. At this time, the primary task is to accelerate these tasks, rather than optimizing the complicated webserver. It can increase the efficiency by 99%.
2. Start a separate thread or process to execute time-consuming tasks. This means that for ioloop, you can enable another thread (or process) to process the off-loading task, so that it can receive other requests instead of blocking.
3. Use asynchronous drivers or library functions to execute tasks, such as gevent and motor.
[Example 1]
import timeimport tornado.ioloopimport tornado.webclass MainHandler(tornado.web.RequestHandler): def get(self): self.write("Hello, world %s" % time.time())class SleepHandler(tornado.web.RequestHandler): def get(self, n): time.sleep(float(n)) self.write("Awake! %s" % time.time())application = tornado.web.Application([ (r"/", MainHandler), (r"/sleep/(\d+)", SleepHandler),])if __name__ == "__main__": application.listen(8888) tornado.ioloop.IOLoop.instance().start()
In this way, http: // localhost: 8888/sleep/10 is enabled on a tab page, and http: // localhost: 8888/is accessed on another tab page /, "Hello world" is not printed until the first page is completed. In fact, the first call blocks the ioloop, causing it to fail to respond to the second request.
[Example 2 -- non-blocking mode]
from concurrent.futures import ThreadPoolExecutorfrom functools import partial, wrapsimport tornado.ioloopimport tornado.webEXECUTOR = ThreadPoolExecutor(max_workers=4)def unblock(f): @tornado.web.asynchronous @wraps(f) def wrapper(*args, **kwargs): self = args[0] def callback(future): self.write(future.result()) self.finish() EXECUTOR.submit( partial(f, *args, **kwargs) ).add_done_callback( lambda future: tornado.ioloop.IOLoop.instance().add_callback( partial(callback, future))) return wrapperclass SleepHandler(tornado.web.RequestHandler): @unblock def get(self, n): time.sleep(float(n)) return "Awake! %s" % time.time()
The unblock modifier submits the modifier to the thread pool and returns a future. Add a callback function in future and assign control to ioloop.
This callback function will eventually call self. Finish and end the request.
Note: The modifier function must be modified by tornado. Web. asynchronous to avoid calling self. Finish too quickly.
Self. Write is NOT thread-safe, so you should not process future results in the main thread.
When you use the @ tornado. Web. Asynchonous modifier, Tornado will never close the connection by itself, and you need to explicitly close self. Finish ().
[Complete demo]
from concurrent.futures import ThreadPoolExecutorfrom functools import partial, wrapsimport time import tornado.ioloopimport tornado.web EXECUTOR = ThreadPoolExecutor(max_workers=4) def unblock(f): @tornado.web.asynchronous @wraps(f) def wrapper(*args, **kwargs): self = args[0] def callback(future): self.write(future.result()) self.finish() EXECUTOR.submit( partial(f, *args, **kwargs) ).add_done_callback( lambda future: tornado.ioloop.IOLoop.instance().add_callback( partial(callback, future))) return wrapper class MainHandler(tornado.web.RequestHandler): def get(self): self.write("Hello, world %s" % time.time()) class SleepHandler(tornado.web.RequestHandler): @unblock def get(self, n): time.sleep(float(n)) return "Awake! %s" % time.time() class SleepAsyncHandler(tornado.web.RequestHandler): @tornado.web.asynchronous def get(self, n): def callback(future): self.write(future.result()) self.finish() EXECUTOR.submit( partial(self.get_, n) ).add_done_callback( lambda future: tornado.ioloop.IOLoop.instance().add_callback( partial(callback, future))) def get_(self, n): time.sleep(float(n)) return "Awake! %s" % time.time() application = tornado.web.Application([ (r"/", MainHandler), (r"/sleep/(\d+)", SleepHandler), (r"/sleep_async/(\d+)", SleepAsyncHandler),]) if __name__ == "__main__": application.listen(8888) tornado.ioloop.IOLoop.instance().start()[Threadpoolexecutor]
The preceding two methods are involved: threadpoolexecutor initialization and submit. For more information, see
class ThreadPoolExecutor(concurrent.futures._base.Executor) | Method resolution order: | ThreadPoolExecutor | concurrent.futures._base.Executor | __builtin__.object | | Methods defined here: | | __init__(self, max_workers) | Initializes a new ThreadPoolExecutor instance. | | Args: | max_workers: The maximum number of threads that can be used to | execute the given calls. | | submit(self, fn, *args, **kwargs) | Submits a callable to be executed with the given arguments. | | Schedules the callable to be executed as fn(*args, **kwargs) and returns | a Future instance representing the execution of the callable. | | Returns: | A Future representing the given call.
1. max_workers can process the maximum number of threads for a given CILS. What if it exceeds this number ??
2. Submit calls FN (* ARGs, ** kwargs) and returns a future instance.
[Future]
Help on class Future in module concurrent.futures._base:class Future(__builtin__.object) | Represents the result of an asynchronous computation. | | Methods defined here: | | __init__(self) | Initializes the future. Should not be called by clients. | | __repr__(self) | | add_done_callback(self, fn) | Attaches a callable that will be called when the future finishes. | | Args: | fn: A callable that will be called with this future as its only | argument when the future completes or is cancelled. The callable | will always be called by a thread in the same process in which | it was added. If the future has already completed or been | cancelled then the callable will be called immediately. These | callables are called in the order that they were added.
[References]
1. http://lbolla.info/blog/2013/01/22/blocking-tornado
2. http://www.tuicool.com/articles/36ZzA3