在 tornado 中异步无阻塞的执行耗时任务


在 linux 上 tornado 是基于 epoll 的事件驱动框架,在网络事件上是无阻塞的。但是因为 tornado 自身是单线程的,所以如果我们在某一个时刻执行了一个耗时的任务,那么就会阻塞在这里,无法响应其他的任务请求,这个和 tornado 的高性能服务器称号不符,所以我们要想办法把耗时的任务转换为不阻塞主线程,让耗时的任务不影响对其他请求的响应。

在 python 3.2 上,增加了一个并行库 concurrent.futures,这个库提供了更简单的异步执行函数的方法。

如果是在 2.7 之类的 python 版本上,可以使用 pip install futures 来安装这个库。

关于这个库的具体使用,这里就不详细展开了,可以去看官方文档,需要注意的是,前两个例子是示例错误的用法,可能会产生死锁。

下面说说如何在 tornado 中结合使用 futures 库,最好的参考莫过于有文档+代码。正好, tornado 中解析 ip 使用的 dns 解析服务是多线程无阻塞的。(netutils.ThreadedResolver)

我们来看看它的实现,看看如何应用到我们的程序中来。

tornado 中使用多线程无阻塞来处理 dns 请求

  1. # 删除了注释
  2. class ThreadedResolver(ExecutorResolver):
  3. _threadpool = None
  4. _threadpool_pid = None
  5. def initialize(self, io_loop=None, num_threads=10):
  6. threadpool = ThreadedResolver._create_threadpool(num_threads)
  7. super(ThreadedResolver, self).initialize(
  8. io_loop=io_loop, executor=threadpool, close_executor=False)
  9. @classmethod
  10. def _create_threadpool(cls, num_threads):
  11. pid = os.getpid()
  12. if cls._threadpool_pid != pid:
  13. # Threads cannot survive after a fork, so if our pid isn't what it
  14. # was when we created the pool then delete it.
  15. cls._threadpool = None
  16. if cls._threadpool is None:
  17. from concurrent.futures import ThreadPoolExecutor
  18. cls._threadpool = ThreadPoolExecutor(num_threads)
  19. cls._threadpool_pid = pid
  20. return cls._threadpool

ThreadedResolver 是 ExecutorEesolver 的子类,看看它的是实现。

  1. class ExecutorResolver(Resolver):
  2. def initialize(self, io_loop=None, executor=None, close_executor=True):
  3. self.io_loop = io_loop or IOLoop.current()
  4. if executor is not None:
  5. self.executor = executor
  6. self.close_executor = close_executor
  7. else:
  8. self.executor = dummy_executor
  9. self.close_executor = False
  10. def close(self):
  11. if self.close_executor:
  12. self.executor.shutdown()
  13. self.executor = None
  14. @run_on_executor
  15. def resolve(self, host, port, family=socket.AF_UNSPEC):
  16. addrinfo = socket.getaddrinfo(host, port, family, socket.SOCK_STREAM)
  17. results = []
  18. for family, socktype, proto, canonname, address in addrinfo:
  19. results.append((family, address))
  20. return results

从 ExecutorResolver 的实现可以看出来,它的关键参数是 ioloop 和 executor,干活的 resolve 函数被@run_on_executor 修饰,结合起来看 ThreadedResolver 的实现,那么这里的 executor 就是from concurrent.futures import ThreadPoolExecutor

再来看看 @run_on_executor 的实现。

run_on_executor 的实现在 concurrent.py 文件中,它的源码如下:

  1. def run_on_executor(fn):
  2. @functools.wraps(fn)
  3. def wrapper(self, *args, **kwargs):
  4. callback = kwargs.pop("callback", None)
  5. future = self.executor.submit(fn, self, *args, **kwargs)
  6. if callback:
  7. self.io_loop.add_future(future,
  8. lambda future: callback(future.result()))
  9. return future
  10. return wrapper

关于 functions.wraps() 的介绍可以参考官方文档 functools — Higher-order functions and operations on callable objects

简单的说,这里对传递进来的函数进行了封装,并用 self.executor.submit() 对包装的函数进行了执行,并判断是否有回调,如果有,就加入到 ioloop 的 callback 里面。

对比官方的 concurrent.futures.Executor 的接口,里面有个 submit() 方法,从头至尾看看ThreadedResolver 的实现,就是使用了 concurrent.futures.ThreadPoolExecutor 这个 Executor 的子类。

所以 tornado 中解析 dns 使用的多线程无阻塞的方法的实质就是使用了 concurrent.futures 提供的ThreadPoolExecutor 功能。


使用多线程无阻塞方法来执行耗时的任务

借鉴 tornado 的使用方法,在我们自己的程序中也使用这种方法来处理耗时的任务。

  1. from tornado.concurrent import run_on_executor
  2. from concurrent.futures import ThreadPoolExecutor
  3. class LongTimeTask(tornado.web.RequestHandler):
  4. executor = ThreadPoolExecutor(10)
  5. @run_on_executor()
  6. def get(self, data):
  7. long_time_task(data)

上面就是一个基本的使用方法,下面展示一个使用 sleep() 来模拟耗时的完整程序。

  1. #!/usr/bin/env python
  2. #-*-coding:utf-8-*-
  3. import tornado.ioloop
  4. import tornado.web
  5. import tornado.httpserver
  6. from concurrent.futures import ThreadPoolExecutor
  7. from tornado.concurrent import run_on_executor
  8. import time
  9. class App(tornado.web.Application):
  10. def __init__(self):
  11. handlers = [
  12. (r'/', IndexHandler),
  13. (r'/sleep/(\d+)', SleepHandler),
  14. ]
  15. settings = dict()
  16. tornado.web.Application.__init__(self, handlers, **settings)
  17. class BaseHandler(tornado.web.RequestHandler):
  18. executor = ThreadPoolExecutor(10)
  19. class IndexHandler(tornado.web.RequestHandler):
  20. def get(self):
  21. self.write("Hello, world %s" % time.time())
  22. class SleepHandler(BaseHandler):
  23. @run_on_executor
  24. def get(self, n):
  25. time.sleep(float(n))
  26. self._callback()
  27. def _callback(self):
  28. self.write("after sleep, now I'm back %s" % time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
  29. if __name__ == "__main__":
  30. app = App()
  31. server = tornado.httpserver.HTTPServer(app, xheaders=True)
  32. server.listen(8888)
  33. tornado.ioloop.IOLoop.instance().start()

此时先调用 127.0.0.1:8888/sleep/10 不会阻塞 127.0.0.1:8888/ 了。

以上,就是完整的在 tornado 中利用多线程来执行耗时的任务。


结语

epoll 的好处确实很多,事件就绪通知后,上层任务函数执行任务,如果任务本身需要较耗时,那么就可以考虑这个方法了,
当然也有其他的方法,比如使用 celery 来调度执行耗时太多的任务,比如频繁的需要写入数据到不同的文件中,我公司的一个项目中,需要把数据写入四千多个文件中,每天产生几亿条数据,就是使用了 tornado + redis + celery 的方法来高效的执行写文件任务。

完。

Tornado异步阻塞解决方案的更多相关文章

  1. Python web框架 Tornado异步非阻塞

    Python web框架 Tornado异步非阻塞   异步非阻塞 阻塞式:(适用于所有框架,Django,Flask,Tornado,Bottle) 一个请求到来未处理完成,后续一直等待 解决方案: ...

  2. Tornado异步非阻塞的使用以及原理

    Tornado 和现在的主流 Web 服务器框架(包括大多数 Python 的框架)有着明显的区别:它是非阻塞式服务器,而且速度相当快.得利于其 非阻塞的方式和对 epoll 的运用,Tornado ...

  3. 5.(基础)tornado异步

    终于到了传说中的异步了,感觉异步这个名字听起来就很酷酷的,以前还不是多擅长Python时,就跑去看twisted的源码,结果给我幼小的心灵留下了创伤.反正包括我在内,都知道异步编程很强大,但是却很少在 ...

  4. {Python之进程} 背景知识 什么是进程 进程调度 并发与并行 同步\异步\阻塞\非阻塞 进程的创建与结束 multiprocess模块 进程池和mutiprocess.Poll

    Python之进程 进程 本节目录 一 背景知识 二 什么是进程 三 进程调度 四 并发与并行 五 同步\异步\阻塞\非阻塞 六 进程的创建与结束 七 multiprocess模块 八 进程池和mut ...

  5. tornado异步(1)

    1. 同步 我们用两个函数来模拟两个客户端请求,并依次进行处理: # coding:utf-8 def req_a(): """模拟请求a""&quo ...

  6. Tornado 异步浅解

    7.1 认识异步 1. 同步 我们用两个函数来模拟两个客户端请求,并依次进行处理: #!/usr/bin/env python3 # -*- coding:utf-8 -*- # @Time: 202 ...

  7. 进程&线程 同步异步&阻塞非阻塞

    2015-08-19 15:23:38 周三 线程 线程安全 如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码 线程安全问题都是由全局变量及静态变量引起的 若每个线程中对 ...

  8. 同步与异步&阻塞与非阻塞

    摘要 一直为同步异步,阻塞非阻塞概念所困扰,特定总结了下,原来是这么个意思 一直为同步异步,阻塞非阻塞概念所困扰,特定总结了下 一.同步与异步的区别 1.概念介绍 同步:所谓同步是一个服务的完成需要依 ...

  9. 【转载】高性能IO设计 & Java NIO & 同步/异步 阻塞/非阻塞 Reactor/Proactor

    开始准备看Java NIO的,这篇文章:http://xly1981.iteye.com/blog/1735862 里面提到了这篇文章 http://xmuzyq.iteye.com/blog/783 ...

随机推荐

  1. 【C++11新特性】 auto关键字

    原文链接: http://blog.csdn.net/xiejingfa/article/details/50469045 熟悉脚本语言的人都知道,很多脚本语言都引入了“类型自动推断”技术:比如pyt ...

  2. 基于Linux的Samba开源共享解决方案测试(四)

    对于客户端的网络监控如图: 双NAS网关100Mb码率视音频文件的稳定读测试结果如下: 100Mb/s负载性能记录 NAS网关资源占用 稳定写 稳定写 CPU空闲 内存空闲 网卡占用 NAS1 8个稳 ...

  3. python之socket编写

    Socket 类型 套接字格式: socket(family,type[,protocal]) 使用给定的地址族.套接字类型.协议编号(默认为0)来创建套接字. socket类型 描述 socket. ...

  4. 解决Visual Studio “无法导入以下密钥文件”的错误

    错误1无法导入以下密钥文件: Common.pfx.该密钥文件可能受密码保护.若要更正此问题,请尝试再次导入证书,或手动将证书安装到具有以下密钥容器名称的强名称 CSP: VS_KEY_ 1110Co ...

  5. Tomcat 支持的Java 版本和兼容性总结

    https://tomcat.apache.org/whichversion.html 最新最全的Tomcat 支持的Java版本对照,即兼容性一览表:   Servlet Spec JSP Spec ...

  6. uva-193-图染色-枚举

    题意:n个节点,可用描成黑色或者白色,黑节点和黑节点不能相连,问最多描出多少黑节点 #include <iostream> #include <stdio.h> #includ ...

  7. quartz 定时任务的增删改

    参考:  https://blog.csdn.net/llmys/article/details/81069863

  8. Ubuntu jdk 8 与 6 切换 (安装与配置)

    Switch To Oracle JDK8 Switch To Oracle JDK8 1.1 Switch Oracle JDK in the Unbuntu 14.04 Step1 : Downl ...

  9. C# ADO.NET 封装的增删改查

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...

  10. 7. mybatis实战教程(mybatis in action)之八:mybatis 动态sql语句

    转自:http://www.kyjszj.com/htzq/79.html 1. if 语句 (简单的条件判断) 2. choose (when,otherwize) ,相当于java 语言中的 sw ...