5

我正在阅读 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 语句就不能做同样的事情吗?我一直在尝试用'yield'来做到这一点,但由于某种原因我无法让它工作...... :(

4

2 回答 2

5

用于此目的的生成器通常称为任务(在许多其他术语中),为了清楚起见,我将在此处使用该术语。是的,有可能。事实上,有几种方法在某些情况下有效且有意义。但是,没有(我知道的)至少有一个gevent.spawngevent.joinall. 更强大和设计良好的那些需要两者的等价物。

根本问题是这样的:生成器可以暂停(当它们命中时yield),但仅此而已。要再次启动它们,您需要一些其他代码来调用next()它们。事实上,你甚至需要调用next()一个新创建的生成器,让它开始做任何事情。同样,生成器本身也不是决定接下来应该运行什么的最佳位置。因此,您需要一个循环来启动每个任务的时间片(将它们运行到下一个yield)并无限期地在它们之间切换。这通常称为调度程序。它们往往很快就会变得非常多毛,所以我不会尝试在一个答案中编写完整的调度程序。但是,我可以尝试解释一些核心概念:

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

最后,您可能对 Greg Ewing关于创建此类调度程序的教程感兴趣。(这是在python-ideas对现在的 PEP 3156 进行头脑风暴时出现的。这些邮件线程也可能对您感兴趣,尽管基于 Web 的存档并不真正适合阅读半年前编写的数十个线程中的数百封邮件。 )

于 2013-02-12T21:28:03.913 回答
2

关键是要意识到你必须提供自己的驾驶循环——我在下面提供了一个简单的演示。我很懒,使用 Queue 对象来提供 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
于 2013-02-12T21:09:58.147 回答