协程vs延续vs生成器


Answers:


127

我将从生成器开始,因为它们是最简单的情况。正如@zvolkov提到的那样,它们是可以重复调用而无需返回的函数/对象,但是在调用时将返回(产生)一个值,然后中止执行。再次调用它们时,它们将从上次暂停执行的位置开始,然后再次执行操作。

生成器本质上是缩减的(不对称)协程。协程和生成器之间的区别在于,协程最初被调用后就可以接受参数,而生成器则不能。

给出一个使用协程的简单示例有点困难,但这是我的最佳尝试。以这个(组成的)Python代码为例。

def my_coroutine_body(*args):
    while True:
        # Do some funky stuff
        *args = yield value_im_returning
        # Do some more funky stuff

my_coro = make_coroutine(my_coroutine_body)

x = 0
while True:
   # The coroutine does some funky stuff to x, and returns a new value.
   x = my_coro(x)
   print x

使用协程的示例是词法分析器和解析器。没有语言上的协程或以某种方式进行仿真,即使将词法分析和解析代码确实是两个独立的问题,也需要将它们混合在一起。但是,使用协程,您可以分离出词法分析代码。

(我将讨论对称和不对称协程之间的区别。只需说它们是等效的,您就可以将它们相互转换,而最不像生成器的非对称协程就是更容易理解。我概述了如何在Python中实现非对称协程。)

延续实际上是非常简单的野兽。它们全部是代表程序中另一点的函数,如果调用它,将导致执行自动切换到该函数代表的点。您每天都使用非常严格的版本,甚至没有意识到。例如,可以将异常视为一种由内而外的延续。我将为您提供延续的基于Python的伪代码示例。

假设Python有一个名为的函数callcc(),该函数带有两个参数,第一个是函数,第二个是用于调用它的参数列表。该函数的唯一限制是它接受的最后一个参数将是一个函数(这将是我们当前的延续)。

def foo(x, y, cc):
   cc(max(x, y))

biggest = callcc(foo, [23, 42])
print biggest

将会发生的情况是callcc()依次调用foo()当前的延续(cc),即对程序中callcc()被调用点的引用。当foo()调用当前的延续,它本质上是一样的,告诉callcc()你正在调用当前的延续价值回归,而当它这样做,它回滚栈当前的延续创建地方,即,当你打电话callcc()

所有这些的结果将是我们假设的Python变体将打印出来'42'

希望能对您有所帮助,并且我肯定我的解释会有所改善!


6
一种观点分隔的延续是函数,但分隔的不是延续:okmij.org/ftp/continuations/undelimited.html#delim-vs-undelim
Frank Shearar 2011年

2
那是个很好的观点。也就是说,在大多数实际应用中,当人们说“继续”时,他们在谈论部分/定界的继续。引入其他各种延续将使解释有些混乱。
基思·高恩

1
连续不是功能,尽管可以将其具体化为功能。“也就是说,在大多数实际应用中,当人们说'continuation'时,他们在谈论部分/定界的延续。” 您是否会指出“延续”一词的这种用法?我从来没有遇到过这样的用法。您还通过使用call / cc给出了一个无界延续的示例。用于定界连续符的运算符通常是“重置”和“移位”(它们可能具有其他名称)。
伊万乔2014年

3
让我们从我撰写本文至今已有五年的事实开始。你参加聚会有点晚了。其次,我知道无限分隔的延续不是函数,但是您在尝试解释它们的工作方式时不引用它们,同时也保持了语言的直接性。从普通程序员的角度来看,无界延续不会返回的事实只会使它成为一次性函数,按照函数的定义这是不正确的,但至少是可以理解的
基思·高恩

2
我参加聚会不晚,因为这是我搜索“协程vs发电机”时在google中获得的第一个结果。我希望找到一些关于它们之间差异的良好信息。无论如何,我在其他地方找到了它。我不是第一个指出您对延续的解释是错误的人。问题是,某人遇到错误时可能会弄错它,并且当他或他遇到用于不同事物的相同单词时可能会感到困惑。
伊万乔2014年

33

协程是轮流完成工作然后暂停以控制组中其他协程的几种程序之一。

延续是您传递给某个过程的“函数的指针”,该过程完成后将被执行(“继续”)。

生成器(在.NET中)是一种语言构造,可以吐出一个值,“暂停”该方法的执行,然后在询问下一个值时从同一点进行处理。


我意识到答案可能并不准确,但是在这个问题上,我尝试保持简单。此外,我自己并不是很了解这一切:)
zvolkov

python中的生成器类似于C#版本,但是作为用于创建迭代器对象实例的特殊语法实现,该实例返回您提供的“函数”定义返回的值。
本森

2
一个小的更正:“ ...包括调用堆栈和所有变量,但不是它们的值”(或只是删除“所有变量”)。连续不保留值,它们仅包含调用堆栈。
2011年

不,延续不是“函数的指针”。在最幼稚的实现中,它包含一个指向函数的指针,一个环境保存局部变量。而且它永远不会返回,除非您使用call / cc之类的东西用返回值捕获它。
NalaGinrut

9

在新版本的Python中,您可以使用 generator.send(),这使python Generators有效地协程。

python生成器与其他生成器(例如greenlet)之间的主要区别在于,在python中,您yield value只能返回到调用者。处于greenlet时,target.switch(value)可以将您带到特定的目标协程,并在target可以继续运行的地方产生一个值。


3
但是在Python中,所有yield调用都必须在同一个函数中,该函数称为“生成器”。您不能yield从子函数中来,这就是为什么Python称为半协程,而Lua具有不对称协程的原因。(有提议提高单产,但我认为这些只是使水
变得

7
@ cdunn2001:(Winston评论)Python3.3引入了“ yield from”表达式,可让您从子生成器中屈服。
Linus Caldwell,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.