Python 使用'进行上下文切换;收益率';
我正在阅读gevent教程,看到了以下有趣的片段: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
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的调用,这将导致尝试重新输入尚未生成的生成器第三,这是一种没有尾部调用消除的语言中的相互递归,因此如果生成器生成太多次,最终会耗尽堆栈。非常有趣。很多我不知道的事情。该教程看起来也不错。干杯