Greenlet简介

Comments(2)


Posted on 2014-02-12 08:11:23 python


Greenlet简介

一个 greenlet 是一个很小的独立微线程。可以把它想像成一个堆栈帧,栈底是初始调用,而栈顶是当前greenlet的暂停位置。你使用greenlet创建一堆这样的堆 栈,然后在他们之间跳转执行。跳转不是绝对的:一个greenlet必须选择跳转到选择好的另一个greenlet,这会让前一个挂起,而后一个恢复。两 个greenlet之间的跳转称为 切换(switch) 。

当你创建一个greenlet,它得到一个初始化过的空堆栈;当你第一次切换到它,他会启动指定的函数,然后切换跳出greenlet。当最终栈底 函数结束时,greenlet的堆栈又编程空的了,而greenlet也就死掉了。greenlet也会因为一个未捕捉的异常死掉。

greenlet又叫协程,是一个伪线程,实际上是串行执行的,其实greenlet不是一种真正的并发机制,而是在同一线程内,在不同函数的执行代码块之间切换,实施“你运行一会、我运行一会”,并且在进行切换时必须指定何时切换以及切换到哪。greenlet的接口是比较简单易用的,但是使用greenlet时的思考方式与其他并发方案存在一定区别。

线程/进程模型在大逻辑上通常从并发角度开始考虑,把能够并行处理的并且值得并行处理的任务分离出来,在不同的线程/进程下运行,然后考虑分离过程可能造成哪些互斥、冲突问题,将互斥的资源加锁保护来保证并发处理的正确性。greenlet则是要求从避免阻塞的角度来进行开发,当出现阻塞时,就显式切换到另一段没有被阻塞的代码段执行,直到原先的阻塞状况消失以后,再人工切换回原来的代码段继续处理。因此,greenlet本质是一种合理安排了的串行,greenlet能够得到比较好的性能表现,主要也是因为通过合理的代码执行流程切换,完全避免了死锁和阻塞等情况。因为greenlet本质是串行,因此在没有进行显式切换时,代码的其他部分是无法被执行到的,如果要避免代码长时间占用运算资源造成程序假死,那么还是要将greenlet与线程/进程机制结合使用(每个线程、进程下都可以建立多个greenlet,但是跨线程/进程时greenlet之间无法切换或通讯)。

from greenlet import greenlet

def test1():
    print 12
    gr2.switch()
    print 34
    gr2.switch()

def test2():
    print 56
    gr1.switch()
    print 78

gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()

上面是一个greenlet的例子 ,有助于我们理解greenlet的概念 。

输出为 12 56 34

Greenlet属性和操作

理解上面的程序先要学习greenlet的操作有哪些:

  greenlet(run=None,parent=None):

创建一个greenlet对象,而不执行。run是回调函数,而parent是父greenlet,缺省是当前greenlet

  greenlet.getcurrent():

获取当前greenlet,也就是谁在调用这个函数。

  greenlet.GreenletExit:

用户杀死一个greenlet,这个异常不会波及到父greenlet。

注意上面的greenlet是指库 greenlet

下面是greenlet对象的方法和属性:

  g.switch(*args, **kwargs) : 切换执行点到 greenlet对象 g 上。

  g.run 调用g,并启动。在g启动后,这个属性就不存在了。

  g.parent g的父greenlet。可写的,但是不允许创建循环的父关系。

  g.gr_frame 当前帧,或者None

  g.dead 判断g是否已经死掉了。

  bool(g) 如果g是活跃的返回True,如果尚未启动或者结束后返回False。

  g.throw([typ,[val,[tb]]]) 切换执行点到 g,但是立即抛出指定的异常到g。如果没有提供参数,异常缺省就是greenlet.GreenletExit。如上面描述的,就是异常波及规则。注意调用这个方法等同于如下:

ef raiser():
      raise typ,val,tb

g_raiser = greenlet(raiser,parent=g)
g_raiser.switch()

关于切换 ,switch()方法是greenlet重要的组成部分。switch()用于greenlet之间的切换,当方法被调用时,这会让执行点跳转到greenlet的switch()被调用处。或者在greenlet死掉时,跳转到父greenlet那里去。在切换时,一个对象或者异常被发送到目标greenlet。例如 :

from greenlet import greenlet

def test1(x,y):
    z= gr2.switch(x+y)
    print "z",z
    print "test1"

def test2(u):
    print "u",u
    gr1.switch(42,20)
    print 'test2'

gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch("hello"," world")

结果:

u hello world
z (42, 20)
test1
test2

如果一个greenlet的run()结束了,他会返回值到父greenlet.如果run()是因异常而终止的,异常会波及到父 greenlet(除非是greenlet.GreenletExit异常,这种情况下,异常会被捕捉并返回到父greenlet)。目标greenlet会接收到发送来的对象作为 switch() 的返回值。虽然 switch() 并不会立即返回,但是它仍然会在未来某一点上返回,当其他greenlet切换回来时。当这发生时,执行点恢复到 switch() 之后,而 switch() 返回刚才调用者发送来的对象。这意味着 x=g.switch(y) 会发送对象y到g,然后等着一个不知道是谁发来的对象,并在这里返回给x。

注意上面的z= gr2.switch(x+y),这儿就是把x = "hello" y="world"发送到 gr2,然后在test2中 gr1.switch(40,20)返回给 z,所以输出的z 是(40,20).这儿还是比较绕的。

注意,任何切换到死掉的greenlet的行为都会切换到greenlet的父greenlet,或者父的父 ,等等。最终的父是永远不会死掉的”main” greenlet。

Greenlet与Python线程

greenlet可以和线程一起使用,在这种情况下,每个线程包含一个独立的main greenlet,并拥有自己的greenlet子树。但是不同的线程之间不能切换greenlet。我觉得提高性能的方法就是“多进程+多线程+协程”这里面是一层包含一层的概念。对于python而言,由于GIL的限制,多线程是无法使用多核CPU的。

Greenlet的垃圾回收机制

如果不再有对greenlet对象的引用时(包括其他greenlet的parent),然后没有办法切换回greenlet。在这种情况下会生成一个 GreenletExit 异常到greenlet。这是greenlet异步接收异常的唯一情况。这给出一个 try .. finally 用于清理greenlet内的资源。这个功能同时允许greenlet中无限循环等待数据和处理数据的编程风格。这样循环可以在最后一个引用消失时自动中断。

  如果希望greenlet死掉或者通过一个新的引用复活,只需要捕捉和忽略 GreenletExit 异常即可达到无限循环。(这句话我的理解是,捕捉GreenletExit就会导致greenlet死掉,忽略会导致greenlet复活)

  greenlet不参与垃圾收集;greenlet帧的循环引用数据会被检测到。将引用传递到其他的循环greenlet会引起内存泄露。

C API 引用

Greenlet可以由C和C++编写的扩展模块或者嵌入python 的程序进行创建和操作。greenlet.h头提供了可供纯python模块调用的全部的API。

Types

PyGreenlet  greenlet.greenlet

Exceptions

PyExc_GreenletError greenlet.error
PyExc_GreenletExit  greenlet.GreenletExit

Reference

PyGreenlet_Import()

一个宏指令,引入greenlet模块,初始化C API。一旦扩展模块使用了greenlet的C API 就必须调用这个。

int PyGreenlet_Check(PyObject *p)

宏指令,如果参数 p是PyGreenlet就返回true

int PyGreenlet_STARTED(PyGreenlet *g)

宏指令,如果greenlet g 已经启动 返回true

int PyGreenlet_ACTIVE(PyGreenlet *g)

宏指令,如果greenlet 对象 g已经启动且没有死亡 返回true

PyGreenlet *PyGreenlet_GET_PARENT(PyGreenlet *g)

宏指令,返回 g的父 greenlet

PyGreenlet *PyGreenlet_SetParent(PyGreenlet *g)

设置 g的父greenlet,如果成功返回0.如果返回-1,g并不是一个指向PyGreenlet的指针 而且会触发 Attribute Error。

PyGreenlet *PyGreenlet_GetCurrent(void)

返回现在活跃状态的greenlet对象

PyGreenlet *PyGreenlet_New(PyObject *run,PyObject *parent)

用于生成一个greenlet对象,参数 run 和 parent都是可选的。如果run 为空,那么greenlet仍然会生成,但是switch的时候就会失败。如果parent为空,则缺省为当前greenlet对象为父。

PyGreenlet *PyGreenlet_Switch(PyGreenlet *g, PyObject *args, PyObject *kwargs)

切换到greenlet对象 g, 后两个参数可为空。如果args为空,那么一个空元素将传递给目标greenlet。如果kwargs为空,那么没有关键参数被传递给目标greenlet。如果参数指定,那么args必定是元祖,kwargs是字典。

PyObject *PyGreenlet_Throw(PyGreenlet *g,PyObject *typ, PyObject *val,PyObject *tb)

切换到greenlet g,但是立即触发异常。typ是异常类型,val是值,tb是可选的,代表回溯对象,可为空。

结束语

关于eventlet 和 gevent 这两个以greenlet为核心的并发框架。两个之间的比较 http://blog.gevent.org/2010/02/27/why-gevent/ ,gevent以libevent为核心,最新版本是libev,eventlet主要以纯python写的事件循环为基础,而且在socket方面存在缺陷。不过我认为年代久远,不足以作为凭证,说不定最新的版本的eventlet已经解决了bug。

gevent学习:见英文版http://sdiehl.github.io/gevent-tutorial/

中文版http://xlambda.com/gevent-tutorial/

eventlet学习 :见 http://eventlet.net/doc/index.html

来源:

http://greenlet.readthedocs.org/en/latest/

http://gashero.yeax.com/?p=112

http://www.elias.cn/Python/PyConcurrency?from=Develop.PyConcurrency

前一篇: libev简介 后一篇: Gevent的hub剖析

Captcha:
验证码

Email:

Content: (Support Markdown Syntax)


Ellsica  2019-07-31 04:14:35 From 127.0.0.1

Tempo Effetto Viagra where can you buy levitra the cheapest Acheter Du Cialis Le Moins Cher Dapoxetina Vs. Serotonina Cheapeast Generic Progesterone Best Website Worldwide On Line


blareenda  2023-02-08 17:13:39 From 127.0.0.1

generic cialis from india 7 times the systemic exposure AUC 0 in humans receiving the MRHD 10 320 mg 60 kg