在 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异步阻塞解决方案的更多相关文章

  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. pig入门案例

    测试数据位于:/home/hadoop/luogankun/workspace/sync_data/pigperson.txt中的数据以逗号分隔 ,zhangsan, ,lisi, ,wangwu, ...

  2. android studio 简介 (上)

    自从android官方宣布不再提供eclipse adt的更新之后,android studio的推进速度超乎想象得快,不管是github上的源码分享,还是stackoverflow上的问题提问,几乎 ...

  3. js中在一个函数中引用另一个函数中的函数,可以这么做

    在另一个函数中,将需要使用的函数绑定在window下 // UEditor $(function () { window.ue = UE.getEditor('editor', { // ue即可成为 ...

  4. python的动态性和_slot_

    python是动态语言 1. 动态语言的定义 动态编程语言 是 高级程序设计语言 的一个类别,在计算机科学领域已被广泛应用.它是一类 在运行时可以改变其结构的语言 :例如新的函数.对象.甚至代码可以被 ...

  5. MySQL优化十大技巧

    转自:https://m.2cto.com/database/201701/557910.html MYSQL优化主要分为以下四大方面: 设计:存储引擎,字段类型,范式与逆范式 功能:索引,缓存,分区 ...

  6. Caused by: java.lang.IllegalStateException: Expected raw type form of org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$Match

    spring 4.0.2,mybatis 3.2.6,aspectjweaver 1.8.10 使用的时候,报错: Caused by: java.lang.IllegalStateException ...

  7. 高性能JSON框架之FastJson的简单使用

    1.前言 1.1.FastJson的介绍: JSON协议使用方便,越来越流行,JSON的处理器有很多,这里我介绍一下FastJson,FastJson是阿里的开源框架,被不少企业使用,是一个极其优秀的 ...

  8. VBA 连接文本的自定义函数(可用于数组公式)

    Function ConTxt(ParamArray args() As Variant) As VariantDim tmptext As Variant, i As Variant, cellv ...

  9. as3 名称

    加密算法方式: https://www.cnblogs.com/wei2yi/p/3484170.html AES  (Advanced Encryption Standard) DES(Data E ...

  10. sqlserver还原数据库

    该方法只针对同等级数据库,不能跨级   比如sqlserver2012还原到sqlserver2008会报错 用数据库日志文件对数据库进行还原一 将日志文件.mdf文件和.ldf文件copy放置在sq ...