设为首页收藏本站

LUPA开源社区

 找回密码
 注册
文章 帖子 博客
LUPA开源社区 首页 业界资讯 技术文摘 查看内容

gevent:轻松异步I/O

2014-9-3 09:33| 发布者: joejoe0332| 查看: 6184| 评论: 0|原作者: gones945, FreeZ|来自: oschina

摘要: 有些奇怪monkey.patch_all()的调用?不用担心,这可不像你每天打的猴子补丁(译注:monkey patching,即动态修改执行代码)。这仅仅是Python发行版恰好要打的一组猴子补丁。 ...


Greenlets/green threads

一个greenlet是一个完整的协程.

例子:

  • gevent

  • greenlet

  • Stackless Python

让我们用gevent重写异步I/O的例子:

1
2
3
4
5
6
7
8
9
10
11
12
import gevent
 
def compute(x, y):
    print "Compute %s + %s ..." % (x, y)
    gevent.sleep(1.0)
    return + y
 
def print_sum(x, y):
    result = compute(x, y)
    print "%s + %s = %s" % (x, y, result)
 
print_sum(12)

(同样的,通过这种方法,我们将启动其他的greenlet以获得规模效应的优势).

这个例子更加简单了!我们可以简单的忽略所有的生成器不一致的地方,因为gevent.sleep()可以触发事件循环(在gevent称作hub),而不需要引入调用帧.同样的,因为我们有顶层的协程,hub可以在需要时实例化,它不需要被创建为栈的显式的父类.

协程提供了从当前栈跳到事件循环,以及从事件循环返回当前栈的神奇功能.

/presentations/gevent-talk/_static/coroutines.svg

上面C语言级别的代码,允许我们编写看起来是同步的,但实际上却所有拥有异步I/O特性的代码.


Gevent: greenlets 和 monkey-patching

我们的代码里要假定没用sleep? 我们可以用gevent的猴子补丁来确保不需要更改代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# These two lines have to be the first thing in your program, before
# anything else is imported
from gevent.monkey import patch_all
patch_all()
 
import time
 
def compute(x, y):
    print "Compute %s + %s ..." % (x, y)
    time.sleep(1.0)
    return + y
 
def print_sum(x, y):
    result = compute(x, y)
    print "%s + %s = %s" % (x, y, result)
 
print_sum(12)

标准库中的大部分阻塞调用都会被打补丁,这样可以用hub调度而不必堵塞。同样的,线程系统会用微线程代替线程。


monkey-patching很糟糕吗?

  在这种情况下,最好把gevent当做python的一部分,它使用了green thread,然后包含标准库代码的不同实现.


  部分原因是,它必须在包含程序入口的模块的最开头.它是在代码运行前,我们使用gevent版本stdlib的一个声明.


monkey patching是优雅的,它允许纯Python应用程序和库在不做修改的情况下变成异步的.它是gevent的一个可选组件,不需要它也可以编写异步程序.

  • 它可以和已存在的同步纯Python代码一起运行

  • 一般也可以和异步代码一起运行,只模拟过select(),但是大多数异步框架都有一个基于select()的实现,像epoll()等函数也是可以实现的.


gevents线程原语的例子

因为gevent是基于轻量级的"线程",所以gevent库包含大量的并发工具来生成greenlet,实现临界区(锁和互斥变量)以及在greenlet之间传递消息.

因为它是一个网路库,所以也包含了一些高级别的网路服务模块,如TCP服务器和WSGI服务器.

生成和杀死greenlets

  • gevent.spawn(function, *args, **kwargs)  - 生成greenlet

  • gevent.kill(greenlet, exception=GreenletExit) - 杀死greenlet是通过在greenlet里面引发异常.

这里也有一些高级别的原语,像gevent.pool,它基于greenlet,与multiprocessing.pool对等.

同步原语

  • gevent.lock.Sempahore

  • gevent.lock.RLock

  • gevent.event.Event

消息传递

  • gevent.queue.Queue

  • gevent.event.AsyncResult - 等待单一结果而阻塞,也允许引发异常.

超时

这里有一个有用的包装器,可以用来杀死一个段时间内都没有成功运行的greenlet.它可以用来在其他一系列操作中引入超时.

1
2
3
4
from gevent import Timeout
 
with Timeout(5):
    data = sock.recv()

更高级别的服务器工具

  • gevent.server.StreamServer - TCP服务器,同时也支持SSL.

  • gevent.server.DatagramServer - UDP服务器.

  • gevent.pywsgi.WSGIServer - WSGI服务器,支持流,保持存活和SSL等.


Gevent I/O模式

使用gevent (避免使用select())时,推荐为各个方面的交互生成一个greenlet--包括读和写.每个greenlet的代码只是一个简单的循环,在需要时可以阻塞.查看早前的chat server 的代码作为示例.

为什么我们想要一个同步的编程模型?

首先,它让代码更易懂易读.它和单线程是一样的编程方式.使用yield后代码不会分散.

更重要的是,上面描述的这些方法,只有gevent不需要改变代码的调用约定.这个重要性不应该被低估,它意味着业务逻辑代码可以通畅的进入到阻塞代码.

一个简单的例子,假设我们想开发一个流式API,之前存在的业务逻辑代码(process_orders)需要一个迭代器.通过gevent,和远程服务器交互过程中,我们可以异步的流化迭代器,而不需要修改代码.

1
2
3
4
5
6
7
8
9
10
11
12
from gevent.socket import create_connection
 
def process_orders(lines):
    """Do something important with an iterable of lines."""
    for in lines:
        ...
 
 
# Create an asynchronous file-like object with gevent
socket = create_connection((host, port))
= socket.makefile(mode='r')
process_orders(f)

另一个优势是,在敏感的地方总可以引发异常.

和真实的线程不同,greenlet不会在任意时间暂停,于是可以使用更少的锁和互斥量.只是在原子操作过程中存在阻塞的风险的时候,需要一个互斥量.

和真实线程的另一个不同是,一个greenlet可以杀死另外一个greenlet--当greenlet下一次恢复的时候触发一个异常.


缺点

坏消息:Python 3分支版本的gevent还没有开发完成.我暂未查明是否它根本不可用.

异步I/O的缺陷

和其他异步I/O框架一样,gevent也有一些缺陷:

  • 阻塞(真正的阻塞,在内核级别)在程序中的某个地方停止了所有的东西.这很像C代码中monkey patch没有生效.你需要采用更细致的方法让C代码库"green".

  • 保持CPU处于繁忙状态.greenlet不是抢占式的,这可能导致其他greenlet不会被调度.

  • 在greenlet之间存在死锁的可能.


总的来说,相对于其他异步I/O框架,gevent的缺陷更少(当然,你可能不能死锁回调,这仅仅是因为,事件循环没有提供同步原始,所以你无法实现临界区).

一个gevent回避的缺陷是,你几乎不会碰到一个和异步无关的Python库--它将阻塞你的应用程序,因为纯Python库使用的是monkey patch的stdlib.

n到m的并发

对于事件而言,一种更具规模效应的方法是,在m个物理线程中运行n个greenlet.在Python中,我们需要进程来做到这点.在多核系统中,这增加了性能,也增加了灵活性.

这是Rust和Java在其他地方使用的模型.


使用gevent的经验

我评估过gevent以及在2011提到的其他系统.Gevent无疑是胜者.虽然在性能上面没太多选择,但是gevent简化编程模型却是一个主要的卖点.不是所有的开发者,在使用生成器,闭包和回调函数的时候都处于相同的水平.但是gevent没有这个要求,你可以使用任何技术让代码更易读.

在已有的代码或与I/O无关的业务逻辑中使用gevent也是很有价值的:你可能希望,在高性能的网络应用程序和线下的批处理进程中重用某个业务逻辑库.


在接下来的18个月里面,我一直在写各种各样的网络应用程序.一个产品是名为nucleon的web服务框架,它的目标是连接RESTful JSON,PostgreSQL和AMQP,所有的都通过"绿色"驱动代码保持高伸缩性.

尽管我们需要修改代码,以非阻塞的方式使用PostgreSQL,但是gevent的mokey patching意味着纯Python驱动在其他数据存储过程中已经正常运行了--所以Redis,ElasticSearch和CouchDB都可以透明的使用.

AMQP库最初是Puka (不是Pika)的一个复制, AMQP库没有强化异步的特性(像Pika一样).我最终完全重写了它,并把它单独作为一个名为nucleon.amqp的项目. nucleon.amqp允许使用完整的同步编程模型和AMQP服务器交互--AMQP的远程队列可以通过Queue API暴露在本地.


在一些并发编程的项目中,也有一些让人抓狂的时候.但是作为一个团队,我们适应了gevent,并且开发了一种图表语言.用它来向各个成员解释,理解各个greenlet之间的数据流向,相互之间如何阻塞以及如何发送信号.

这个项目成功了.我们保持代码简洁和可维护,同时也保证服务高效和可伸缩(负载测试中保留的)


酷毙

雷人

鲜花

鸡蛋

漂亮
  • 快毕业了,没工作经验,
    找份工作好难啊?
    赶紧去人才芯片公司磨练吧!!

最新评论

关于LUPA|人才芯片工程|人才招聘|LUPA认证|LUPA教育|LUPA开源社区 ( 浙B2-20090187 浙公网安备 33010602006705号   

返回顶部