tornado异步编程与node.js异步编程

以梦为马

tornado是由FaceBook开源的一个异步python框架,node.js是由Joyent资助的开源项目,致力于提供一套编写高性能并发Web应用的JavaScript框架。这篇博客就简单谈一谈两种异步框架在Linux下的异同,如果有错误或者表达不恰当的地方欢迎各路大神指正。

在Linux下,node.js靠libev和libeio配合使用来实现异步I/O。libev是一个事件驱动库,基于poll/epoll提供的I/O复用方式,主要用于事件驱动的网络编程。libeio是异步版的POSIX API,主要负责文件I/O操作,他仅仅依赖于pthread,可以和libev配合使用。libev和libeio都是通过c和c++来实现的,所以node.js的底层是c和c++。

tornado的异步主要是通过 ioloop和iostream这2个模块来实现的。ioloop是tornado自己实现的事件驱动模型,和libev一样,也是对poll/epoll的封装,他使用_handlers保存(fd,handler)的映射关系,_events保存就绪的fd以及对应的events事件,tornado提供了add_handler, update_handler、remove_handler这3个方法对handler进行操作。然后调用start函数就可以开始对所有的fd进行轮询,并且调用对应的handler。而iostream是对ioloop的一层封装,主要实现socket的异步调用,主要由以下的3个方法实现读写操作read_until, read_bytes, write。

从异步的实现方式上面看,在Linux下2者底层都是对poll/epoll的封装,只是一个底层由c实现,另一个完全由python实现。

从代码风格上讲我觉得tornado比node.js有优势,node.js是函数式编程,异步操作处理得到的结果只能通过回调函数进行处理,假如说我们需要进行多级的函数调用,并且结果互相依赖,那么这个时候就只能在回调函数里面嵌套回调函数,这样的代码可读性和可维护性都不高,比如:

当我们需要读取一个文件的时候,我们会执行以下语句:

fs.readFile('/path', function (err, data) {
  if (err) throw err;
  console.log(data);
});

当我们需要读取一个文件的内容,并且根据这个文件的内容读写另外一个文件的时候,我们就需要执行以下语句:

fs.readFile('/path', function (err, data) {
  if (err) throw err;
  fs.readFile(data, function (err, data2) {
    if (err) throw err;
    // 在这里处理data2的数据
  });
});

这个地方我们就在回调里面嵌套了一层回调,这里仅仅是2层,假如回调嵌套层数很多的话代码就不容易读懂,看起来结构就很乱。

tornado为了解决这个问题提供了一种使用同步的方式编码,并且使用异步的方式执行的方法。解决方式就是使用python的yield关键字和gen模块。

gen模块可以把一个函数调用里面的回调函数的参数当成返回值赋给变量,如果有多个参数就返回一个tuple。但是这个并没有解决异步调用时间差的问题,如果直接使用这个方法的话得到的值是None,因为回调函数刚刚加入轮询队列,还没有被执行,这个时候gen就尝试读取参数并且返回,这个时候得到的就是None。这个时候就需要yield关键字,这个关键字可以把一个函数变成一个生成器,简单的来说就是调用一个带有yield关键字的函数的时候,函数会先顺序执行到yield这一句,然后函数被挂起,当这个函数再一次被执行的时候,函数会从yield这一句接着往下执行。如果yield和gen搭配使用的话,函数会先执行到yield这一句,然后把调用的函数加入轮询队列,然后函数被挂起,等待轮询里面的函数执行完毕返回的时候函数再次被调用,通过gen取出回调函数里面的参数并且赋值给变量,函数接着往下执行。例如:

如果不使用gen模块的话异步调用需要这样写:

class AsyncHandler(RequestHandler):
    @asynchronous
    def get(self):
        http_client = AsyncHTTPClient()
        http_client.fetch("http://example.com",
                          callback=self.on_fetch)

    def on_fetch(self, response):
        do_something_with_response(response)
        self.render("template.html")

on_fetch就是回调函数

如果使用gen模块代码可以这样写:

class GenAsyncHandler(RequestHandler):
    @gen.coroutine
    def get(self):
        http_client = AsyncHTTPClient()
        response = yield http_client.fetch("http://example.com")
        do_something_with_response(response)
        self.render("template.html")

代码中使用了@gen.coroutine修饰器,并且通过yield关键字将异步的代码变成同步的写法。这样代码的逻辑就很符合大多数人的编程思维,大大增强了代码的可读性。

总的来说node.js和tornado都是不错的异步编程工具,对于高并发都有很强的处理能力,node.js更适合全栈式的前后端分离的开发,tornado则是一个轻量级的python框架,他的优势更多的是在对websocket和长连接的支持,知乎就是使用tornado开发的。

阅读 6.1k

以梦为马的专栏
长期更新开发过程中所用的技术,爬过的坑

2012级本科生,正在找工作

174 声望
14 粉丝
0 条评论

2012级本科生,正在找工作

174 声望
14 粉丝
文章目录
宣传栏