Python 使用'进行上下文切换;收益率';

Python 使用'进行上下文切换;收益率';,python,gevent,Python,Gevent,我正在阅读gevent教程,看到了以下有趣的片段: import gevent def foo(): print('Running in foo') gevent.sleep(0) print('Explicit context switch to foo again') def bar(): print('Explicit context to bar') gevent.sleep(0) print('Implicit context swi

我正在阅读gevent教程,看到了以下有趣的片段:

import gevent

def foo():
    print('Running in foo')
    gevent.sleep(0)
    print('Explicit context switch to foo again')

def bar():
    print('Explicit context to bar')
    gevent.sleep(0)
    print('Implicit context switch back to bar')

gevent.joinall([
    gevent.spawn(foo),
    gevent.spawn(bar),
])
其中的执行流程如下所示:foo->bar->foo->bar。 如果没有gevent模块,但使用yield语句,是否不可能执行相同的操作?
我一直试图用“收益率”来实现这一点,但由于某种原因,我无法让它发挥作用……:(

关键是要意识到,您必须提供自己的驱动循环—我在下面提供了一个简单的演示。我很懒,使用队列对象来提供FIFO,我已经有一段时间没有在重要的项目中使用python了

#!/usr/bin/python

import Queue

def foo():
    print('Constructing foo')
    yield
    print('Running in foo')
    yield
    print('Explicit context switch to foo again')

def bar():
    print('Constructing bar')
    yield
    print('Explicit context to bar')
    yield
    print('Implicit context switch back to bar')

def trampoline(taskq):
    while not taskq.empty():
        task = taskq.get()
        try:
            task.next()
            taskq.put(task)
        except StopIteration:
            pass

tasks = Queue.Queue()
tasks.put(foo())
tasks.put(bar())

trampoline(tasks)

print('Finished')
运行时:

$ ./coroutines.py 
Constructing foo
Constructing bar
Running in foo
Explicit context to bar
Explicit context switch to foo again
Implicit context switch back to bar
Finished

用于此目的的生成器通常被称为任务(在许多其他术语中),为了清楚起见,我在这里使用该术语。是的,这是可能的。事实上,有几种方法在某些上下文中有效且有意义。但是,没有一种(我知道)对于
gevent.spawn
gevent.joinall
中的至少一个,在没有等效项的情况下工作。功能更强大、设计更完善的版本要求两者都具有等效项

基本问题是:生成器可以挂起(当它们点击
yield
时),但仅此而已。要再次启动它们,您需要对它们调用其他代码
next()
。事实上,您甚至需要对新创建的生成器调用
next()
,以便它开始执行任何操作。 类似地,生成器本身并不是决定下一步应该运行什么的最佳位置。因此,您需要一个循环来启动每个任务的时间片(将它们运行到下一个
产生
)并在它们之间无限期切换。这通常被称为调度程序。它们往往会很快变得非常复杂,因此我不会尝试在一个答案中编写完整的调度程序。不过,我可以尝试解释一些核心概念:

  • 人们通常将
    yield
    视为将控制权交还给调度程序(实际上类似于代码中的
    gevent.sleep(0)
    )。这意味着,生成器做它想做的任何事情,当它位于上下文切换方便且可能有用的位置时,它将
    yield
    s
  • 在Python 3.3+中,
    yield from
    是一个非常有用的工具,可以委托给另一个生成器。如果不能使用它,则必须让调度程序模拟调用堆栈并将返回值路由到正确的位置,并执行类似
    result=yield subtask()的操作
    在您的任务中。这会更慢、更复杂,而且不太可能产生有用的堆栈跟踪(
    yield from
    免费进行此操作)。但直到最近,它还是我们最好的
  • 根据您的使用情况,您可能需要多种工具来管理任务。常见的示例包括生成更多任务、等待任务完成、等待多个任务中的任何一个完成、检测失败(未捕获异常)其他任务等。这些任务通常由调度程序处理,并为任务提供与调度程序通信的API。完成此通信的一种简洁(但并不总是完美)方法是生成特殊值
  • 基于生成器的任务和gevent(以及类似的库)之间的一个相当重要的区别是后者中的上下文切换是隐式的,而任务使得识别上下文切换变得很简单:只有
    产生[from]的东西
    可以运行调度程序代码。例如,只需查看代码,而无需检查它调用的任何内容,就可以确定一段代码是否是原子的(w.r.t.其他任务;如果将线程添加到混合中,则必须单独担心它们)

最后,您可能会对Greg Ewing关于创建这样一个调度器的文章感兴趣。(这是在对现在的PEP 3156进行头脑风暴时,在
python ideas
上提出的。您可能也对这些邮件线程感兴趣,尽管基于web的归档文件并不适合阅读半年前编写的几十个线程中的数百封邮件。)

很有意义。也感谢您的演示。我想知道generatorA是否有可能产生next(generatorB)(generatorB产生next(generatorA)等等)。这有三个问题:第一个问题是,我不知道生成器有什么方法可以自动生成,这是设置相互递归所必需的。第二个问题是,在计算yield语句之前,需要返回对next/0的调用,这将导致尝试重新输入尚未生成的生成器第三,这是一种没有尾部调用消除的语言中的相互递归,因此如果生成器生成太多次,最终会耗尽堆栈。非常有趣。很多我不知道的事情。该教程看起来也不错。干杯