介绍#

Gevent是一个 coroutine 基于 Python 使用的网络库 greenletlibevlibuv 事件循环。

功能包括:

GEvent是 inspired by eventlet 但它具有更一致的API、更简单的实现和更好的性能。阅读其他人的原因 use gevent 并查看 open source projects based on gevent .

Gevent是由 Denis Bilenko .

从1.1版开始,GEvent由Jason Madden维护 NextThought (通过GEvent 21)和 Institutional Shareholder Services 得到了来自 contributors 并根据麻省理工学院的许可证获得许可。

what's new 在最新的主要版本中。

查看详细信息 changelog 对于此版本。

例子#

下面的示例演示如何并发运行任务。

>>> import gevent
>>> from gevent import socket
>>> urls = ['www.google.com', 'www.example.com', 'www.python.org']
>>> jobs = [gevent.spawn(socket.gethostbyname, url) for url in urls]
>>> _ = gevent.joinall(jobs, timeout=2)
>>> [job.value for job in jobs]
['74.125.79.106', '208.77.188.166', '82.94.164.162']

在工作产生之后, gevent.joinall() 等待它们完成,最多允许2秒钟。然后通过检查 value 属性。这个 gevent.socket.gethostbyname() 函数具有与标准相同的接口 socket.gethostbyname() 但是,它不会阻塞整个解释器,从而让其他的greenlet无障碍地继续处理他们的请求。

猴子修补#

上面使用的示例 gevent.socket 用于套接字操作。如果标准 socket 使用了模块。由于DNS请求是连续的(序列化的),因此完成该示例需要3倍的时间。在greenlets中使用标准的socket模块会使gevent变得毫无意义,那么在 socket (包括标准库模块,如 urllib )?

这就是猴子修补的地方。中的函数 gevent.monkey 小心地替换标准中的函数和类 socket 模块及其合作伙伴。这样,即使不知道gevent的模块也可以从在多个greenlet环境中运行中获益。

>>> from gevent import monkey; monkey.patch_socket()
>>> import requests # it's usable from multiple greenlets now

示例concurrent_download.py .

小技巧

通过观察事件可以了解猴子的修补过程。 gevent.monkey 发射。

超出插座#

当然,标准库还有其他几个部分可以阻塞整个解释器并导致序列化行为。gevent也提供了许多这样的合作版本。它们可以通过单独的功能单独进行修补,但是大多数使用monkey修补的程序都希望使用 gevent.monkey.patch_all() 功能:

>>> from gevent import monkey; monkey.patch_all()
>>> import subprocess # it's usable from multiple greenlets now

小技巧

当猴子修补时,建议在整个过程中尽早这样做。如果可能的话,猴子补丁应该是执行的第一行。稍后进行猴子修补,特别是如果创建了本机线程, atexit 或者信号处理程序已安装,或者套接字已创建,可能导致不可预测的结果,包括意外的 LoopExit 错误。

事件循环#

GEvent没有阻塞和等待套接字操作完成(一种称为轮询的技术),而是安排操作系统传递一个事件,让它知道何时到达要从套接字读取的数据。这样做之后,gevent可以继续运行另一个greenlet,也许它本身已经准备好了一个事件。注册事件并在事件到达时对其作出反应的重复过程是事件循环。

与其他网络库不同,尽管与eventlet的方式类似,gevent在专用greenlet中隐式地启动事件循环。没有 reactor 你必须打电话给 run()dispatch() 功能开启。当gevent的api中的函数想要阻塞时,它将获取 gevent.hub.Hub 实例——运行事件循环的特殊greenlet——并切换到它(据说greenlet 屈服 控制中心)。如果没有 Hub 但是,会自动创建一个实例。

小技巧

每个操作系统线程都有自己的线程 Hub . 这使得可以从多个线程(小心地)使用gevent阻塞API。

默认情况下,事件循环使用系统上可用的最佳轮询机制。

备注

gevent.core 模块。此模块没有文档记录,不用于一般用途,其确切内容和语义根据使用libev或libuv事件循环而略有变化。提供给事件循环API的回调在 Hub 因此,greenlet不能使用同步的gevent api。在那里可以使用异步API,比如 gevent.spawn()gevent.event.Event.set() .

协同多任务#

greenlet都在同一个OS线程中运行,并且是协同调度的。这意味着,在某个特定的greenlet放弃控制之前,(通过调用将切换到 Hub )其他绿党就没有机会竞选了。对于I/O绑定的应用程序来说,这通常不是一个问题,但是在执行CPU密集型操作或调用绕过事件循环的阻塞I/O函数时,应该注意这一点。

小技巧

甚至一些明显的合作功能,比如 gevent.sleep() 在某些情况下,可以暂时优先于等待I/O操作。

在大多数情况下,同步访问跨greenlet共享的对象是不必要的(因为生成控制通常是显式的),因此传统的同步设备如 gevent.lock.BoundedSemaphoregevent.lock.RLockgevent.lock.Semaphore 类虽然存在,但并不是经常使用。线程和多处理的其他抽象在协作世界中仍然有用:

轻量级伪线程#

通过创建一个 Greenlet 实例并调用其 start 方法。(The gevent.spawn() 函数就是这样做的快捷方式)。这个 start 方法调度一个到greenlet的开关,该开关将在当前greenlet放弃控制时立即发生。如果有多个活动greenlet,它们将以未定义的顺序逐个执行,因为它们各自放弃对 Hub .

如果在执行过程中出现错误,它将无法逃脱greenlet的界限。未处理的错误会导致打印stacktrace,并由失败函数的签名和参数进行注释:

>>> glet = gevent.spawn(lambda : 1/0); glet.join()
>>> gevent.sleep(1)
Traceback (most recent call last):
 ...
ZeroDivisionError: integer division or modulo by zero
<Greenlet at 0x7f2ec3a4e490: <function <lambda...>> failed with ZeroDivisionError

回溯异步打印到 sys.stderr 当绿叶树死去时。

Greenlet 实例有许多有用的方法:

  • join --等待绿叶树退出;

  • kill --中断Greenlet的执行;

  • get --返回greenlet返回的值,或重新引发终止该值的异常。

小菜可以小菜一族。这种方法的一个用途是通过子类化 Greenlet 类并重新定义其 __str__ 方法。有关详细信息,请参阅 Greenlet子类 .

绿信子可以从另一个绿信子同步杀死。杀戮将恢复沉睡中的格林莱特,但不是继续执行,而是 GreenletExit 将被提升。

>>> from gevent import Greenlet
>>> g = Greenlet(gevent.sleep, 4)
>>> g.start()
>>> g.kill()
>>> g.dead
True

这个 GreenletExit 异常及其子类的处理方式与其他异常不同。提升 GreenletExit 不被视为异常情况,因此不打印回溯。这个 GreenletExit 由返回 get 就好像是绿叶树送回的,不是升起的。

这个 kill 方法可以接受要引发的自定义异常:

>>> g = Greenlet.spawn(gevent.sleep, 5) # spawn() creates a Greenlet and starts it
>>> g.kill(Exception("A time to kill"))
Traceback (most recent call last):
 ...
Exception: A time to kill
Greenlet(5) failed with Exception

这个 kill 也可以接受 超时 参数,指定等待greenlet退出的秒数。注意 kill 不能保证目标greenlet不会忽略异常(即它可能会捕捉到异常),因此最好总是将超时传递给 kill (否则,负责杀戮的格林莱特将永远被封锁)。

小技巧

由于以下原因在目标greenlet内引发异常的确切时间 kill 未定义。有关详细信息,请参阅该函数的文档。

小心

杀死greenlet时要小心,尤其是由库生成的任意greenlet或执行您不熟悉的代码时。如果正在执行的代码不准备处理异常,则对象状态可能已损坏。例如,如果它获得了 Lock 但是 使用A finally 阻止释放它,在错误的时间杀死greenlet可能导致锁被永久锁定:

def func():
    # DON'T DO THIS
    lock.acquire()
    socket.sendall(data) # This could raise many exceptions, including GreenletExit
    lock.release()

This document 描述线程的类似情况。

Greenlet还充当上下文管理器,因此您可以在一行中组合生成和等待Greenlet完成:

>>> def in_greenlet():
...     print("In the greenlet")
...     return 42
>>> with Greenlet.spawn(in_greenlet) as g:
...     print("In the with suite")
In the with suite
In the greenlet
>>> g.get(block=False)
42

超时#

gevent api中的许多函数是同步的,在操作完成之前会阻塞当前的greenlet。例如, kill 等到目标绿let dead 返回前 [1]. 通过传递关键字参数,可以使这些函数中的许多成为异步函数。 block=False .

此外,许多同步函数接受 超时 参数,指定函数可以阻塞的时间限制(示例包括 gevent.event.Event.wait()gevent.Greenlet.join()gevent.Greenlet.kill()gevent.event.AsyncResult.get() 以及更多)。

这个 socketSSLObject 实例也可以有超时,由 settimeout 方法。

当这些还不够时, gevent.Timeout 类和 gevent.with_timeout() 可用于将超时添加到(合作的、产生的)代码的任意部分。

进一步阅读#

要限制并发性,请使用 gevent.pool.Pool 类(见) 示例dns_mass_resolve.py

GEvent附带了TCP/SSL/HTTP/WSGi服务器。见 实现服务器 .

Gevent有许多配置选项。见 配置GEvent 有关详细信息。本文档还解释了如何启用gevent的内置监视和调试功能。

中的对象 gevent.util 可能有助于监视和调试。

API引用 完整的API参考。

外部资源#

Gevent for working Python developer _是一个综合教程。

脚注