• 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

  • 相关阅读:
    阿里巴巴、腾讯、百度的面试问题笔知识汇总(两)
    ORM武器:NHibernate(三)五个步骤+简单对象CRUD+HQL
    SIGPIPE并产生一个信号处理
    Duanxx的Altium Designer学习:PCB试想一下,在目前的水平
    网络工程师课程---6、应用层(应用层的功能是什么)
    网络工程师课程---5、传输层(传输层常用协议有哪些)
    网络工程师课程---4、网络层(网关是什么)
    网络工程师课程---3、IP与路由器(ip地址的主要作用是什么)
    网络工程师课程---2、物理层和数据链路层(物理层的作用是什么)
    交换机与路由器与猫的区别与联系
  • 原文地址:https://www.cnblogs.com/tracylining/p/3509465.html
Copyright © 2020-2023  润新知