【发布时间】:2019-02-12 05:41:31
【问题描述】:
协程与延续和生成器有什么区别?
【问题讨论】:
-
我想知道协程和延续是否有效等价。我知道可以用延续来建模协程,但是是否可以用协程来建模延续,因为延续严格来说更强大?
标签: python generator coroutine continuations
协程与延续和生成器有什么区别?
【问题讨论】:
标签: python generator coroutine continuations
我将从生成器开始,因为它们是最简单的情况。正如@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 中实现非对称协程。)
Continuations 实际上是非常简单的野兽。它们都是代表程序中另一个点的函数,如果你调用它,将导致执行自动切换到函数代表的点。您每天都在使用非常受限的版本,甚至没有意识到这一点。例如,异常可以被认为是一种由内而外的延续。我会给你一个基于 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'。
我希望这会有所帮助,我相信我的解释可以改进很多!
【讨论】:
协程是轮流执行其工作然后暂停以将控制权交给组中的其他协程的几个程序之一。
Continuation 是传递给某个过程的“指向函数的指针”,当该过程完成时将被执行(“继续”)。
生成器(在 .NET 中)是一种语言结构,可以吐出一个值,“暂停”方法的执行,然后在被要求输入下一个值时从同一点继续。
【讨论】:
在较新版本的 Python 中,您可以使用 generator.send() 向生成器发送值,这使得 python 生成器有效地协程。
python 生成器和其他生成器(比如 greenlet)的主要区别在于,在 python 中,您的 yield value 只能返回给调用者。在 greenlet 中,target.switch(value) 可以将您带到特定的目标协程并产生一个值,target 将继续运行。
【讨论】:
yield 调用必须在同一个函数中,该函数称为“生成器”。你不能在子函数中yield,这就是为什么 Python 被称为 semi-coroutines,而 Lua 有 asymmetric coroutines。 (有提高产量的建议,但我认为那些只会使水变得浑浊。)