使用'yield'进行上下文切换

时间:2013-02-12 20:44:32

标签: 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 switch back to bar')

gevent.joinall([
    gevent.spawn(foo),
    gevent.spawn(bar),
])

执行流程就像这个foo - >吧 - > foo - >吧。 如果没有gevent模块但是使用yield语句,是不是可以做同样的事情? 我一直试图用'收益'来做这件事,但由于某种原因我不能让它工作...... :(

2 个答案:

答案 0 :(得分:5)

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

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

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

最后,您可能会对Greg Ewing tutorial感兴趣创建这样的调度程序。 (这是在python-ideas上进行的,同时对现在的PEP 3156进行了头脑风暴。这些邮件线程也可能对您感兴趣,尽管基于Web的存档并不适合在几十个写入的线程中读取数百封邮件半年前。)

答案 1 :(得分: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