Tornado异步阻塞解决方案
在 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 请求
# 删除了注释
class ThreadedResolver(ExecutorResolver):
_threadpool = None
_threadpool_pid = None
def initialize(self, io_loop=None, num_threads=10):
threadpool = ThreadedResolver._create_threadpool(num_threads)
super(ThreadedResolver, self).initialize(
io_loop=io_loop, executor=threadpool, close_executor=False)
@classmethod
def _create_threadpool(cls, num_threads):
pid = os.getpid()
if cls._threadpool_pid != pid:
# Threads cannot survive after a fork, so if our pid isn't what it
# was when we created the pool then delete it.
cls._threadpool = None
if cls._threadpool is None:
from concurrent.futures import ThreadPoolExecutor
cls._threadpool = ThreadPoolExecutor(num_threads)
cls._threadpool_pid = pid
return cls._threadpool
ThreadedResolver
是 ExecutorEesolver
的子类,看看它的是实现。
class ExecutorResolver(Resolver):
def initialize(self, io_loop=None, executor=None, close_executor=True):
self.io_loop = io_loop or IOLoop.current()
if executor is not None:
self.executor = executor
self.close_executor = close_executor
else:
self.executor = dummy_executor
self.close_executor = False
def close(self):
if self.close_executor:
self.executor.shutdown()
self.executor = None
@run_on_executor
def resolve(self, host, port, family=socket.AF_UNSPEC):
addrinfo = socket.getaddrinfo(host, port, family, socket.SOCK_STREAM)
results = []
for family, socktype, proto, canonname, address in addrinfo:
results.append((family, address))
return results
从 ExecutorResolver
的实现可以看出来,它的关键参数是 ioloop
和 executor
,干活的 resolve
函数被@run_on_executor
修饰,结合起来看 ThreadedResolver
的实现,那么这里的 executor
就是from concurrent.futures import ThreadPoolExecutor
再来看看 @run_on_executor
的实现。
run_on_executor
的实现在 concurrent.py
文件中,它的源码如下:
def run_on_executor(fn):
@functools.wraps(fn)
def wrapper(self, *args, **kwargs):
callback = kwargs.pop("callback", None)
future = self.executor.submit(fn, self, *args, **kwargs)
if callback:
self.io_loop.add_future(future,
lambda future: callback(future.result()))
return future
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 的使用方法,在我们自己的程序中也使用这种方法来处理耗时的任务。
from tornado.concurrent import run_on_executor
from concurrent.futures import ThreadPoolExecutor
class LongTimeTask(tornado.web.RequestHandler):
executor = ThreadPoolExecutor(10)
@run_on_executor()
def get(self, data):
long_time_task(data)
上面就是一个基本的使用方法,下面展示一个使用 sleep() 来模拟耗时的完整程序。
#!/usr/bin/env python
#-*-coding:utf-8-*-
import tornado.ioloop
import tornado.web
import tornado.httpserver
from concurrent.futures import ThreadPoolExecutor
from tornado.concurrent import run_on_executor
import time
class App(tornado.web.Application):
def __init__(self):
handlers = [
(r'/', IndexHandler),
(r'/sleep/(\d+)', SleepHandler),
]
settings = dict()
tornado.web.Application.__init__(self, handlers, **settings)
class BaseHandler(tornado.web.RequestHandler):
executor = ThreadPoolExecutor(10)
class IndexHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world %s" % time.time())
class SleepHandler(BaseHandler):
@run_on_executor
def get(self, n):
time.sleep(float(n))
self._callback()
def _callback(self):
self.write("after sleep, now I'm back %s" % time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
if __name__ == "__main__":
app = App()
server = tornado.httpserver.HTTPServer(app, xheaders=True)
server.listen(8888)
tornado.ioloop.IOLoop.instance().start()
此时先调用 127.0.0.1:8888/sleep/10 不会阻塞 127.0.0.1:8888/ 了。
以上,就是完整的在 tornado 中利用多线程来执行耗时的任务。
结语
epoll 的好处确实很多,事件就绪通知后,上层任务函数执行任务,如果任务本身需要较耗时,那么就可以考虑这个方法了,
当然也有其他的方法,比如使用 celery 来调度执行耗时太多的任务,比如频繁的需要写入数据到不同的文件中,我公司的一个项目中,需要把数据写入四千多个文件中,每天产生几亿条数据,就是使用了 tornado + redis + celery 的方法来高效的执行写文件任务。
完。
Tornado异步阻塞解决方案的更多相关文章
- Python web框架 Tornado异步非阻塞
Python web框架 Tornado异步非阻塞 异步非阻塞 阻塞式:(适用于所有框架,Django,Flask,Tornado,Bottle) 一个请求到来未处理完成,后续一直等待 解决方案: ...
- Tornado异步非阻塞的使用以及原理
Tornado 和现在的主流 Web 服务器框架(包括大多数 Python 的框架)有着明显的区别:它是非阻塞式服务器,而且速度相当快.得利于其 非阻塞的方式和对 epoll 的运用,Tornado ...
- 5.(基础)tornado异步
终于到了传说中的异步了,感觉异步这个名字听起来就很酷酷的,以前还不是多擅长Python时,就跑去看twisted的源码,结果给我幼小的心灵留下了创伤.反正包括我在内,都知道异步编程很强大,但是却很少在 ...
- {Python之进程} 背景知识 什么是进程 进程调度 并发与并行 同步\异步\阻塞\非阻塞 进程的创建与结束 multiprocess模块 进程池和mutiprocess.Poll
Python之进程 进程 本节目录 一 背景知识 二 什么是进程 三 进程调度 四 并发与并行 五 同步\异步\阻塞\非阻塞 六 进程的创建与结束 七 multiprocess模块 八 进程池和mut ...
- tornado异步(1)
1. 同步 我们用两个函数来模拟两个客户端请求,并依次进行处理: # coding:utf-8 def req_a(): """模拟请求a""&quo ...
- Tornado 异步浅解
7.1 认识异步 1. 同步 我们用两个函数来模拟两个客户端请求,并依次进行处理: #!/usr/bin/env python3 # -*- coding:utf-8 -*- # @Time: 202 ...
- 进程&线程 同步异步&阻塞非阻塞
2015-08-19 15:23:38 周三 线程 线程安全 如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码 线程安全问题都是由全局变量及静态变量引起的 若每个线程中对 ...
- 同步与异步&阻塞与非阻塞
摘要 一直为同步异步,阻塞非阻塞概念所困扰,特定总结了下,原来是这么个意思 一直为同步异步,阻塞非阻塞概念所困扰,特定总结了下 一.同步与异步的区别 1.概念介绍 同步:所谓同步是一个服务的完成需要依 ...
- 【转载】高性能IO设计 & Java NIO & 同步/异步 阻塞/非阻塞 Reactor/Proactor
开始准备看Java NIO的,这篇文章:http://xly1981.iteye.com/blog/1735862 里面提到了这篇文章 http://xmuzyq.iteye.com/blog/783 ...
随机推荐
- 【C++11新特性】 auto关键字
原文链接: http://blog.csdn.net/xiejingfa/article/details/50469045 熟悉脚本语言的人都知道,很多脚本语言都引入了“类型自动推断”技术:比如pyt ...
- 基于Linux的Samba开源共享解决方案测试(四)
对于客户端的网络监控如图: 双NAS网关100Mb码率视音频文件的稳定读测试结果如下: 100Mb/s负载性能记录 NAS网关资源占用 稳定写 稳定写 CPU空闲 内存空闲 网卡占用 NAS1 8个稳 ...
- python之socket编写
Socket 类型 套接字格式: socket(family,type[,protocal]) 使用给定的地址族.套接字类型.协议编号(默认为0)来创建套接字. socket类型 描述 socket. ...
- 解决Visual Studio “无法导入以下密钥文件”的错误
错误1无法导入以下密钥文件: Common.pfx.该密钥文件可能受密码保护.若要更正此问题,请尝试再次导入证书,或手动将证书安装到具有以下密钥容器名称的强名称 CSP: VS_KEY_ 1110Co ...
- Tomcat 支持的Java 版本和兼容性总结
https://tomcat.apache.org/whichversion.html 最新最全的Tomcat 支持的Java版本对照,即兼容性一览表: Servlet Spec JSP Spec ...
- uva-193-图染色-枚举
题意:n个节点,可用描成黑色或者白色,黑节点和黑节点不能相连,问最多描出多少黑节点 #include <iostream> #include <stdio.h> #includ ...
- quartz 定时任务的增删改
参考: https://blog.csdn.net/llmys/article/details/81069863
- Ubuntu jdk 8 与 6 切换 (安装与配置)
Switch To Oracle JDK8 Switch To Oracle JDK8 1.1 Switch Oracle JDK in the Unbuntu 14.04 Step1 : Downl ...
- C# ADO.NET 封装的增删改查
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...
- 7. mybatis实战教程(mybatis in action)之八:mybatis 动态sql语句
转自:http://www.kyjszj.com/htzq/79.html 1. if 语句 (简单的条件判断) 2. choose (when,otherwize) ,相当于java 语言中的 sw ...