原文:http://www.pywave.com/2012/08/17/about-gevent-and-tornado/

还是前几月的时候,几乎在同一时间,自己接触到了 Gevent 和 Tornado 这两个已经不新的东西,那时那个 思绪混乱啊!似乎都支持异步,似乎都是无阻塞(non-blocking),性能似乎都好到个不行 (猛击)。知道两者虽是单线程, 但基于无阻塞的特性,战斗力那个是嗖嗖地上涨,运用得当的话,hold住上K个连接不是问题。虽然很感兴趣,虽然完全没弄清楚两者内里的实质,但为了完成工作,略略了解了基本的应用后,卷起手袖就上啦。 当然,作为一个有志的程序员,在满足了现实的迫切需要后,一颗渴望知其所以然的心便开始蠢蠢欲动

从 Tornado 说起

刚开始,对 Tornado 的感觉最为新鲜,在官网介绍里其是一个无阻塞的Web服务器以及相关工具的集合,但 个人更为倾向其为一个颇为完备的微型 web 框架。Tornado 性能好的关键是其无阻塞异步的特性,但这魔术 似的效果是如何达成的呢?迷思与困惑。我那小脑袋里的思维还停留于多进程(多线程)那样的并发模型中, 实在有点难以理解 Tornado 的异步机制。

通过查阅各式文章以及源代码,整体的框架脉络开始逐渐在脑海中显现出来。其实,Tornado 的异步模型 是由事件驱动以及特定的回调函数(callback)所组成的!一直没有弄明白,Tornado 具体是如何实现 无阻塞异步,当清楚了事件驱动和回调函数的概念后,事情似乎又变得简单起来了。

对于一般的程序,在执行阶段若遇到 I/O 事件,整个进程将被阻塞住,直到 I/O 事件结束,程序又继续执行。 接设我们对一些 I/O 事件进行了定制,使其可以立即返回(即无阻塞),那么程序将能立即继续执行。但 问题又来了,那当 I/O 事件完成后又该怎么办呢?此时,回调函数的威力就出来了,我只需要将进行特定 处理的回调函数与该 I/O 事件绑定起来,当该 I/O 事件完成后就调用绑定的回调函数,就可以处理具体的 I/O 事件啦。啊,似乎还有一个问题,回调函数要如何与 I/O 事件绑定起来?最简单的想法是,直接通过 一个 while True 循环不断的轮询,当检测到 I/O 事件完成了即触发回调函数。但是,这样的效率当然不会 高,利用系统中高效的 I/O 事件轮询机制(epoll on Linux, kqueue on most BSD)就是最明智的 解决方案。于是,无阻塞 I/O +事件驱动+高效轮询方式便组成了 Tornado 的异步模型。

Tornado 的核心是 ioloop 和 iostream 这两个模块,前者提供了一个高效的 I/O 事件循环,后者则封装了 一个无阻塞的 socket 。通过向 ioloop 中添加网络 I/O 事件,利用无阻塞的 socket ,再搭配相应的回调 函数,便可达到梦寐以求的高效异步执行啦。多说无益,来看一下具体的示例:

from tornado import ioloop
from tornado.httpclient import AsyncHTTPClient urls = ['http://www.google.com', 'http://www.yandex.ru', 'http://www.python.org'] def print_head(response):
print ('%s: %s bytes: %r' % (response.request.url,
len(response.body),
response.body[:50])) http_client = AsyncHTTPClient()
for url in urls:
print ('Starting %s' % url)
http_client.fetch(url, print_head)
ioloop.IOLoop.instance().start()

因为使用了 AsyncHTTPClient 来处理请求操作,整个示例是异步执行的,即三个url请求无等待的依次发出。 我们可以看到 fetch 方法使用了 print_head 函数来作为回调函数,这意味着,当 fetch 完成了请求操作, 相应的 print_head 函数便会被触发调用。恩,... 额,...,乍看起来,使用 Tornado 进行异步编程似乎 并不难,让人跃跃欲试。但实际上,在现实生活中,事件驱动的编程还是会很费脑力,需要一定的创造性思维。 不过,这也许是 Tornado 受欢迎的原因之一呢。 :)

再来看下 Gevent

Gevent 是基于协程(coroutine)实现的 Python 网络库,使用了轻量级的 greenlet 作为执行单元,并 基于 libevent 事件循环构建了直观的调用接口。

当时看到这样的描述,脑袋的第一反应是,协程??稍稍了解后,发现协程其实也不是什么高深的概念,协程 也被称为微线程,一看这别名就知道跟线程应该很类似。作为类比倒也可以这么认为,两者关键的区别在于, 线程是由系统进行调度的,而协程是由用户自己进行调度的。当知道这一事实后,立刻想到,这自行调度灵活 肯定是会很灵活,但要调度的话可是很有难度的吧?调度的方法暂时不谈,除了更为灵活外,自行调度的直接 结果当然就是省去了系统调度(什么用户态转内核态,以及什么 context switch),因此协程间切换的资源 消耗很小,再配合协程生成成本很低的另一特点,这可真是相当的美妙。事实上,Python 语言本身就支持基础 的协程的概念,generator 是其中的产物(这里)。

对于 Gevent,其使用的协程实际上就是 greenlet 。当你使用 greenlet 生成了一些协程,就可以在这些 协程里不断跳转执行,两个 greenlet 之间的跳转被称为切换(switch)。通过切换,我们就可以实现对协程 的调度。还应该知道的是,每个 greenlet 都拥有一个父 greenlet ,这是在 greenlet 初始化时就确定的。 当一个 greenlet 执行完毕后,执行权会切换到其父 greenlet 中。实际上,所有的 greenlet 会被组织成 一颗树,树根便是最“老资格”的 greenlet ,这个老 greenlet 确定了各 greenlet 间的逻辑关系。

上面说到协程必须自行调度,不会是要自己构造一个调度器吧?这当然可以做到,但不是必须,因为 Gevent 已经基于 greenlet 和 libevent 封装了许多基础常用的库,例如 socket 、event 和 queue 等,只要使用 这些库进行开发,或者对使用的标准库或第三方库打一下补丁(monket patch),就能保证生成的各协程在 I/O 等待时正确地进行切换,从而实现无阻塞的异步执行。

刚接触 Gevent 时,感觉跟传统的并发编程很类似,但了解渐深后,才发现这货实际上跟 Tornado 更为类似。 因为, Gevent 本质上也是事件驱动。实现的策略可以是,在将要执行 I/O 阻塞事件时,先在事件循环中对该事件 进行注册,关联的回调函数便是对当前协程的切换操作(current_greenlet.switch()),注册成功后即 切换回当前协程的父协程中进行执行(current_greenlet.parent.switch())。当注册的 I/O 事件被 触发后,事件循环在恰当时机便会执行该回调函数,也就是切换到原先的协程继续执行程序。从而,就实现 无阻塞的 I/O 事件处理。怎样,是否感觉相当的有趣? :)

Gevent 了不得的地方还在于,我们能像编写一般程序那样来编写异步程序,这可是弥足珍贵。为了更直观的 显示,让我们来看一下具体的运行示例:

import gevent
from gevent import monkey
# patches stdlib (including socket and ssl modules) to cooperate with other greenlets
monkey.patch_all() import urllib2 urls = ['http://www.google.com', 'http://www.yandex.ru', 'http://www.python.org'] def print_head(url):
print ('Starting %s' % url)
data = urllib2.urlopen(url).read()
print ('%s: %s bytes: %r' % (url, len(data), data[:50])) jobs = [gevent.spawn(print_head, url) for url in urls] gevent.joinall(jobs)

上面示例做的事情实际上跟前面 Tornado 的示例是一样,同样是异步的对url进行请求。在我看来,使用 Gevent 进行编程,无论是可读性还是可操作性都能让人满意。但也要清楚,在实际操作中,为了达到较理想 效果,经常还是需要根据不同的情况对代码进行一些相应的“雕琢”。还有一点很常被人忽略, Gevent 是 基于协程实现的 Python 网络库,其适用面更多的是在于网络 I/O 频繁的需求里,很多情况下 Gevent 可能 并不是很好的选择。总的来说,Gevent 确实很讨人喜爱,性能好,开销小,代码易维护,是广大 pythoner 手中的一大利器。

总要总结一下

作为一名 Python 程序员,在探究和使用 Tornado 与 Gevent 的过程里,除了得到许多思考的乐趣外,最 让人高兴的是收获了一些全新的视野。使用 Python 编程的好处之一便是,可以很容易地跳出语言的框框去看 各式问题,从而提高自己对于程序设计的总体认识。人生苦短,我用Python! :-)

gevent和tornado异步的更多相关文章

  1. 浅谈 Gevent 与 Tornado(转)

    原文:http://www.pywave.com/2012/08/17/about-gevent-and-tornado/ 还是前几月的时候,几乎在同一时间,自己接触到了 Gevent 和 Torna ...

  2. 5.(基础)tornado异步

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

  3. tornado异步请求的理解(转)

    tornado异步请求的理解 http://www.kankanews.com/ICkengine/archives/88953.shtml 官网第一段话: Tornado is a Python w ...

  4. 使用Tornado异步接入第三方(支付宝)支付

    目前国内比较流行的第三方支付主要有支付宝和微信支付,博主最近研究了下如何用Python接入支付宝支付,这里我以Tornado作为web框架,接入支付宝构造支付接口. 使用Tornado异步接入支付宝支 ...

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

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

  6. Python Web框架 tornado 异步原理

    Python Web框架 tornado 异步原理 参考:http://www.jb51.net/article/64747.htm 待整理

  7. Tornado异步(2)

    Tornado异步 因为epoll主要是用来解决网络IO的并发问题,所以Tornado的异步编程也主要体现在网络IO的异步上,即异步Web请求. 1. tornado.httpclient.Async ...

  8. tornado异步(1)

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

  9. tornado异步请求响应速度的实例测试

    tornado异步请求响应速度的实例测试

随机推荐

  1. 微信公众号菜单添加小程序,miniprogram,pagepath参数详解,php开发公众号

    随着微信小程序功能的开发, 已经可以跟公众号打通了, 主要有两种方式: 1) 在公众号文章中插入小程序 2) 在公众号菜单中添加小程序 第一种方式, 子恒老师在前面的课程已经详细介绍过, 今天来讲第二 ...

  2. C# 中的集合(Array/ArrayList/List<T>/HashTable/Dictionary)

    int [] numbers = new int[5]; // 长度为5,元素类型为 int. string[,] names = new string[5,4]; // 5*4 的二维数组 byte ...

  3. python-day18--匿名函数

    一.lambda表达式 1.匿名函数的核心:一些简单的需要用函数去解决的问题,匿名函数的函数体只有一行 2.参数可以有多个,用逗号隔开 3.返回值和正常的函数一样可以是任意的数据类型 4.练习: 请把 ...

  4. 关于InputStream类的available()方法

    要一次读取多个字节时,经常用到InputStream.available()方法,这个方法可以在读写操作前先得知数据流里有多少个字节可以读取.需要注意的是,如果这个方法用在从本地文件读取数据时,一般不 ...

  5. java使用HttpClient

    HttpClient常用的包有两个 org.apache.http.client以及org.apache.commons.httpclient 我常用的是org.apache.http.client. ...

  6. oaf 动态创建table vo (转)

    原文地址:如何动态创建table 需求: 因为系统中有几千个QA plan 但是不能手动创建几千个 质量收集页面所有需要根据 不同的plan 动态创建对应的 质量收集页面. 但是创建tabel 都要绑 ...

  7. jw

    1.  ISO/OSI参考模型(七层)  应表会传 传输介质(双绞线,同轴电缆等)不在OSI7个层次之内 电气特性:电压表示0,1: 机械特性:接口形状,尺寸等 过程特性:出现顺序及信号线的工作原理 ...

  8. scrapy-redis基础和介绍

    一.scrapy-redis(0.6)依赖的环境 Scrapy >= 1.0.0                #终于过了1版本,这个太重要了,总算坑小了点,感谢那些为了解决各种scrapy与s ...

  9. Java——使用File类递归遍历指定路劲下的所有文件

    body, table{font-family: 微软雅黑} table{border-collapse: collapse; border: solid gray; border-width: 2p ...

  10. DevExpress WPF入门指南:如何自动或手动添加DXSplashScreen控件

    <DevExpress v17.2 版本更新公开课>点击报名 DevExpress WPF 的 DXSplashScreen 控件在应用加载的时候显示一个启动界面.添加DXSplashSc ...