[译文][转发]greenlet:轻量级并发程序

英文原版的书文地址:https://greenlet.readthedocs.io/en/latest/
汉语翻译转发地址:https://zhuanlan.zhihu.com/p/25188731

greenlets的垃圾堆回收生命周期

万一对一个greenlet的兼具涉及都已经失效(包蕴来自其余greenlets中的父级属性的关联),那时候,没有别的一种艺术切换回该greenlet中。那种景色下,格林letExit分外将会时有发生。那是七个greenlet接受异步执行的唯一办法。使用try:finally:语句块来清理被那一个greenlet使用的财富。这种性质量管理协会理一种编制程序风格,greenlet无限循环等待数据同时实施。当对该greenlet的最终关联失效,那种循环将自动终止。

比方greenlet要么回老家,要么依照留存有个别地点的涉及恢复生机。只须求捕获与忽略只怕造成极端循环的格林letExit。

格林lets不加入垃圾回收。循环那个在greenlet框架中的数据时候,那一个数量将不会被检查和测试到。循环的积存别的greenlets的引用将或许引致内部存储器泄漏。

异常

Type namePython
namePyExc_GreenletErrorgreenlet.errorPyExc_GreenletExitgreenlet.GreenletExit

C API 相关

格林lets能够透过用C/C++写的扩展模块来扭转与维护,只怕来自于嵌入到python中的应用。greenlet.h
头文件被提供,用来展现对原生的python模块的全体的API访问。

父级greenlet

让我们看看当greenlet谢世的时候,程序执行到哪个地方去了。每2个greenlet都有1个父级greenlet。最初的父级是创办greenlet的那七个greenlet(父级greenlet是足以在任曾几何时候被改变)。父级greenlet是当二个greenlet离世的时候程序继续执行的地点。那种办法,程序组织成一颗树。不在用户成立的greenlet中运作的顶层代码在隐式的主greenlet中运转,它是堆栈数的根。

在上边的事例中,gr1与gr2将主greenlet作为父级greenlet。无论它们中的何人执行实现,程序执行都会重回到”main”greenlet中。

向来不捕获的百般将抛出到父级greenlet中。举个例子,假如地点的test2()包涵1个语法错误,它将生成一个杀死gr2的NameError错误,那几个荒唐将一贯跳转到主greenlet。错误堆栈将显示test2,而不会是test1。须要留意的是,switches不是调用,而是程序在互动的”stack
container(堆栈容器)”直接实施的跳转,“parent”定义了逻辑上位居当前greenlet之下的库房。

关联

  • PyGreenlet_Import():1个宏定义,导入greenlet模块,初叶化C
    API。必须在每二个用到greenlet C API的模块中调用一次。
  • int PyGreenlet_Check(PyObject
    *p):一个宏定义,若是参数是Py格林let重返true。
  • int PyGreenlet_STARTED(PyGreenlet
    *g):八个宏定义,假如greenlet在开端了回到true。
  • int PyGreenlet_ACTIVE(PyGreenlet
    *g):3个宏定义,假若greenlet在移动中回到true。
  • PyGreenlet *PyGreenlet_GET_PARENT(PyGreenlet
    *g):贰个宏定义,再次来到greenlet中的父级greenlet。
  • int PyGreenlet_SetParent(PyGreenlet *g, PyGreenlet
    *nparent):设置父级greenlet。再次回到0为设置成功,-1,表示g不是一卓有效能的Py格林let指针,AttributeError将抛出。
  • PyGreenlet
    *PyGreenlet_GetCurrent(void):再次回到当前活蹦乱跳的greenlet对象。
  • PyGreenlet *PyGreenlet_New(PyObject *run, PyObject
    *parent):使用run与parent创制1个新的greenlet对象。那八个参数是可选的。如若run是NULL。这一个greenlet创制,假若切换起初将破产。假若parent是NULL。那几个parent将自行设置成当前greenlet。
  • PyObject *PyGreenlet_Switch(PyGreenlet *g, PyObject *args,
    PyObject *kwargs):切换来greenet
    g。args与kwargs是可选的,能够为NULL。如若args为NULL,1个空的tuple将发送给目的greenlet
    g。若是kwargs是NULL的。没有key-value参数发送。若是钦定参数,那么args应该是多少个tuple,kwargs应该是3个dict。
  • PyObject *PyGreenlet_Throw(PyGreenlet *g, PyObject *typ,
    PyObject *val, PyObject *tb):切换成greenlet
    g,并且立即抛出typ参数(引导的值val)钦命的老大,调用堆栈对象tb是可选的,并且能够为NULL。

切换

当在1个greenlet中调用方法switch(),在greenlet之间的切换将发出,平常状态下,程序执行跳转到switch()被调用的greenlet中。可能当一个greenlet过逝,程序执行将跳转到父级greenlet程序中,当发生切换的时候,一个对象或贰个13分被发送到指标greenlet中。那是一种在八个greenlet中传递音讯的便宜的法门。举个例子:

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

def test2(u):
    print u
    gr1.switch(42)

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

已与事先例子一样顺序执行,它将会打字与印刷“hello
world”与42。多说一句,test1(),test2()的参数不是在greenlet创立的时候给的,而是在率先次切换的时候给出。

此处给出了有关发送的数码的领会的规则:

g.switch(*args, **kwargs):切换执行到greenlet
g,发送数据,作为一个特种的例证,借使g没有进行,它将初叶实施。

对此将死的greenlet。当run()完毕的时候,将会产生对象给父级greenlet。要是greenlet因为格外而平息,那个那3个将会抛出到父级greenlet中(greenlet.格林letExit例外,这么些十二分被捕获了同时平素退出到父级greenlet中)。

除去上面例子描述的,平日目的greenlet(父级greenlet)接收此前调用switch()挂起,执行完结重回的再次回到值作为结果。事实上,固然对switch()的调用不会立马回到结果,可是当其余部分greenlet切换回来的时候,在今后的有个别点将会回去结果。当切换产生的时候,程序将在它从前挂起的地方苏醒。switch()自个儿回来发生的对象。那就意味着x=g.switch(y)将y给g,稍后将回来从有个别不关乎的greenlet中回到的不涉及的指标给x变量。

提示一下,任何试图切换来二个逝世的greenlet的将会走到驾鹤归西greenlet的父级,恐怕父级的父级,以此类推(最后的父级是“main”
greenlet,它是从未有过会死掉的)。

指鹿为马堆栈协助

当使用greenlet的时候,标准的python错误堆栈与讲述将不会安份守己预期的运营,因为堆栈与框架的切换发生在同等的线程中。使用守旧的不二法门可信赖的检查和测试greenlet切换是一件很劳碌的业务。由此,为了精雕细刻对greenlet基础代码的调剂,错误堆栈,难点讲述的支撑,在greenlet模块中,有局地新的点子:

  • greenlet.gettrace():再次回到先前已部分调用堆栈方法,只怕None。
  • greenlet.settrace(callback):设置2个新的调用堆栈方法,再次回到中期已部分艺术依然None。当有些事件时有发生时,那些回调函数被调用,能够永安里做一下信号处理。

      def callback(event, args):
          if event == 'switch':
              origin, target = args
              # Handle a switch from origin to target.
              # Note that callback is running in the context of target
              # greenlet and any exceptions will be passed as if
              # target.throw() was used instead of a switch.
              return
          if event == 'throw':
              origin, target = args
              # Handle a throw from origin to target.
              # Note that callback is running in the context of target
              # greenlet and any exceptions will replace the original, as
              # if target.throw() was used with the replacing exception.
              return
    

    为了同盟,当事件依然是switch要么是throw,而不是别的恐怕的风浪时候,将参数解包成tuple。这样,API恐怕扩张出于sys.settrace()相似的新的风云。

Greenlets与python的线程

格林lets将得以和python线程结合起来。这种境况下,每三个线程包罗三个单独的涵盖八个子greenlets树的“main”
greenlet。混合或切换在分化线程中的greenlets是不容许的作业。

类型

Type namePython namePyGreenletgreenlet.greenlet

目录与表

greenlets的法门与质量

  • g.switch(*args, **kwargs):切换程序到greenlet g中推行,参见下面。
  • g.run:当它开始的时候,g的回调将会被实施,当g已经发轫实施了,那特本性将不会设有了。
  • g.parent:父级greenlet。那是可编写制定属性,可是不可见写成了死循环。
  • g.gr_frame:最顶层的构造,只怕等于None。
  • g.dead: bool值,当g死亡了,值为True。
  • bool(g):bool值,当重临结构是True,表示g还活跃,假诺是False,表示它病逝了依旧还没起来。
  • g.throw([typ, [val,
    [tb]]]):切换成g执行,可是及时抛出2个加以的不胜。假如没有参数提供,暗中认可非常是greenlet.格林letExit。同地方描述一样,符合规律的丰硕传递规则生效。调用该方法同上面代码是大致也就是的:

      def raiser():
          raise typ, val, tb
      g_raiser = greenlet(raiser, parent=g)
      g_raiser.switch()
    

    有有些例外的是,那段代码不能够用于greenlet.格林letExit非常,这一个可怜将不会从g_raiser传播到g。

实例化对象

greenlet.greenlet是四个体协会程类型,它辅助一下操作:

  • greenlet(run=None,parent=None):创设三个新的greenlet对象(还平昔不发轫运转)。run是叁个可调用的函数,用来被调用。parent定义父级greenlet,暗中同意是当下greenlet。
  • greenlet.getcurrent():获取当前greenlet(即,调用该函数的greenlet)
  • greenlet.格林letExit:那些奇异的卓殊不会抛出到父级greenlet中,那足以用来杀死一个纯净的greenlet。

greenlet类型能够被子类化。通过调用在greenlet创设的时候开头化的run属性来实施二个greenlet。但是对于子类来说,定义四个run方法比提供贰个run参数给构造器更有意义。

背景

greenlet包是Stackless的衍生产品,它是2个援助微线程(叫tasklets)的CPython版本。Tasklets运转在伪并发方式下(平日在三个或有限的OS级其他线程),他们通过“channels”来交互数据。

贰头来说,
二个“greenlet”任然是二个从未中间调度的关于微线程的比较原始的概念。换句话说,当您想要在你代码运转时做到准确控制,“greenlet”是一种很有用的点子。在greenlet基础之上,你可以定义自身的微线程调度策略。不管怎么样,greenlets也足以以一种尖端控制流结构的章程用于他们协调。举个例子,大家得以另行生成迭代器。python自带的生成器与greenlet的生成器之间的区分是greenlet的生成器能够嵌套调用函数,并且嵌套函数也会yield值(补充表明的是,你不要求动用yield关键词,参见例子:test_generator.py)。

序言

“greenlet”
是小型的独自的伪线程。考虑到作为二个帧堆栈。最远的帧(最底部)是你调用的早期的函数,最外侧的帧(最顶层)是在脚下greenlet被压进去的。当您利用greenlets的时候是经过创办一多级的这种堆栈,然后在他们中间跳转执行。那种跳转将会造成原先的帧挂起,最终的帧从挂起状态苏醒。在greenlets之间的跳转关系叫做“switching(切换)”。

当你创立多个greenlet,它将有3个初步化的空堆栈。当你首先次切换成它,它初步运转2个具体的函数。在那一个函数中或者调用其余的函数,从此时此刻greenlet中切换出去,等等。当最尾部的函数实现实施,greenlet的栈再一次为空,这时,greenlet与世长辞。greenlet也说不定应贰个未捕获的丰裕而截止。

举个例证:

from greenlet import greenlet

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

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

gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()
  • 末段一行跳转到test1, 然后打字与印刷12,
  • 跳转到test2, 然后打字与印刷56
  • 跳转回test1, 打字与印刷34,
    test1完成,并且gr1病逝。与此同时,程序执行再次来到到gr1.switch()调用。
  • 必要证实的是78向来都未曾打字与印刷。

用法

例子

大家来考虑贰个用户输入指令的终端控制台系统。假诺输入是每个字符输入。在这么的叁个系统中,有个出色的循环如下所示:

def process_commands(*args):
    while True:
        line = ''
        while not line.endswith('\n'):
            line += read_next_char()
        if line == 'quit\n':
            print("are you sure?")
            if read_next_char() != 'y':
                continue    # ignore the command
        process_command(line)

明天,假诺你将先后移植到GUI程序中,绝超越二分之一的GUI成套工具是依照事件驱动的。他们为每叁个用户字符输入调用3个回调函数。(将“GUI”替换来“XML
expat
parser”,对你的话应该特别熟谙了)。在这么的图景中,执行上边包车型客车函数read_next_char()是很不便的。那里是四个不匹配的函数:

def event_keydown(key):
    ??

def read_next_char():
    ?? should wait for the next event_keydown() call

您可能考虑用线程的办法来完成那么些了。greenlets是另一种不须求关联锁与从不当机难题的可选的缓解方案。你执行process_commands(),独立的greenlet。通过如下格局输入字符串。

def event_keydown(key):
         # jump into g_processor, sending it the key
    g_processor.switch(key)

def read_next_char():
        # g_self is g_processor in this simple example
    g_self = greenlet.getcurrent()
        # jump to the parent (main) greenlet, waiting for the next key
    next_char = g_self.parent.switch()
    return next_char

g_processor = greenlet(process_commands)
g_processor.switch(*args)   # input arguments to process_commands()

gui.mainloop()

那么些事例中,执行流程如下:

  • 当作为g_processor
    greenlet一有的的read_next_char()函数被调用,所以当接到到输入切换来下面greenlet,
    程序复苏到主循环(GUI)执行。
  • 当GUI调用event_keydown()的时候,程序切换成g_processor。那就象征程序跳出,无论它被挂起在那些greenlet什么地点。在这些例子中,切换来read_next_char(),并且在event_keydown()中被按下的key作为switch()的结果再次来到给了read_next_char()。

急需验证的是read_next_char()的挂起与还原都保留其调用堆栈。以便在prprocess_commands()中依照他来的地方苏醒到不一样的岗位。那使得以一种好的支配流来控制造进度序逻辑成为大概。大家无需完全的重写process_commands(),将其转移为状态机。

相关文章