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

kai*_*ilo 4 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),
])
Run Code Online (Sandbox Code Playgroud)

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

小智 5

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

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

  • 人们通常认为将yield控制权交给sheduler(实际上类似于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的头脑风暴时出现的.这些邮件线程也可能对您感兴趣,尽管基于网络的存档不太适合在半年前写入的几十个线程中阅读数百封邮件. )