greenlet微线程

Greenlet简介

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

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

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

 1 from greenlet import greenlet
 2 
 3 def test1():
 4     print 12
 5     gr2.switch()
 6     print 34
 7     gr2.switch()
 8 
 9 def test2():
10     print 56
11     gr1.switch()
12     print 78
13 
14 gr1 = greenlet(test1)
15 gr2 = greenlet(test2)
16 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。如上面描述的,就是异常波及规则。注意调用这个方法等同于如下: 

def 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

Type namePython name
PyGreenlet greenlet.greenlet

Exceptions

Type namePython name
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

后续的学习中我将对两个框架进行实践。当然如果使用twisted也是一个很好的解决方案。

来源: 

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

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

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

原文地址:https://www.cnblogs.com/tracylining/p/3509465.html