在 Python 中使用协程实现 "SystemCalls"

Implementing "SystemCalls" with Coroutines in Python

我目前正在阅读教程文档 http://www.dabeaz.com/coroutines/Coroutines.pdf 并卡在了(纯协程)多任务部分,特别是系统调用部分。

让我困惑的部分是

class Task(object):
    taskid = 0
    def __init__(self,target):
        Task.taskid += 1
        self.tid = Task.taskid # Task ID
        self.target = target # Target coroutine
        self.sendval = None # Value to send

    def run(self):
        return self.target.send(self.sendval)


def foo():
    mytid = yield GetTid()
    for i in xrange(5):
        print "I'm foo", mytid
        yield


class SystemCall(object):
    def handle(self):
        pass

class Scheduler(object):
    def __init__(self):
        self.ready = Queue()
        self.taskmap = {}

    def new(self, target):
        newtask = Task(target)
        self.taskmap[newtask.tid] = newtask
        self.schedule(newtask)
        return newtask.tid

    def schedule(self, task):
        self.ready.put(task)

    def mainloop(self):
        while self.taskmap:
        task = self.ready.get()
        try:
            result = task.run()
            if isinstance(result,SystemCall):
                result.task = task
                result.sched = self
                result.handle()
                continue
            except StopIteration:
                self.exit(task)
                continue
        self.schedule(task)

实际调用

sched = Scheduler()
sched.new(foo())
sched.mainloop()

我不明白的部分是,tid 是如何在 foo() 中分配给 mytid 的?事物的先后顺序,好像是这样的(从sched.mainloop()开始)。流程有误请指正

假设:让我们说出一些事情

the_coroutine = foo()
scheduler = Scheduler
the_task = scheduler.new(the_coroutine) # assume .new() returns the task instead of tid
  1. 调度器:.mainloop()被调用
  2. 调度器:the_task.run()被调用
  3. the_task: the_coroutine.send(无)被调用
  4. the_corou: yield GetTid(),并且returns一个GetTid的实例给scheduler在3中的None发送到循环中的yield语句之前。 (我说得对吗?)
  5. the_corou:(也是同时?)myTid被赋值为GetTid()的实例?
  6. 调度器:result = theTask.run() the_task.run()
  7. 调度器:result确实是SystemCall的一个实例
  8. 调度器:result.handle()被调用
  9. GetTid 实例:scheduler.schedule(the_task)
  10. 调度程序:当前迭代已完成,开始新的迭代
  11. 调度程序:(假设队列中没有其他任务)the_task.run() 被调用
  12. 调度器:the_coroutine.send()被调用
  13. 我迷路了?

当它到达第 12 步时,显然循环已经开始并且能够在调度程序运行相应任务时打印 tid。然而,foo()中的mytid的tid值到底是什么时候赋值的呢?我确定我在流程中遗漏了一些东西,但是在哪里(甚至完全错误)?

然后我注意到任务对象调用 .send() 的部分,它 returns 一个值,所以 .send() returns 一个值?

您似乎省略了 Scheduler 的 new 方法,这几乎肯定是分配发生的地方。我想象 GetTid() 产生的结果会立即使用 .send() 发送回协程。

关于 .send(),是的,你是对的,.send() return是一个值。

尝试下面的例子看看发生了什么。 .send 为 = yield 左侧的变量赋值,协程内的代码继续执行,直到遇到下一个 yield。在这一点上,协同程序产生,无论它产生什么,都是 .send.

的 return 值
>>> def C():
...     x = yield
...     yield x**2
...     print 'no more yields left'
...
>>> cor = C()
>>> cor.next()
>>> yielded = cor.send(10)
>>> print yielded
100
>>> cor.next()
no more yields left
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

将部分评论点移至答案中

那么,x = yield y 是如何运作的?

当协程遇到该行代码时,它 yields 值 y,然后停止执行,等待有人调用它的带有参数的 .send() 方法。

当有人确实调用 .send() 时,无论 .send 中的参数是什么都被分配给变量 x,并且协程从那里开始执行代码,直到它的下一个点yield 声明。

编辑: 天哪,它变得更复杂了……我之前浏览过 David Beazley 的演讲,但老实说,我更熟悉他的另外两个演讲在发电机... 通过链接 material,看起来 GetTid 的这个定义就是我们所追求的。

class GetTid(SystemCall):
    def handle(self):
        self.task.sendval = self.task.tid
        self.sched.schedule(self.task)

我引用他的演讲:"The operation of this is little subtle"。哈哈

现在看看主循环:

if isinstance(result,SystemCall):
    result.task = task
    result.sched = self
    result.handle() # <- This bit!
    continue

result 这里是 GetTid 对象,运行 它的 handle 方法设置它的 tasksendval属性到任务的 tid,然后通过将任务放回队列来安排任务。

一旦从队列中检索到任务,task.run() 方法又是 运行。让我们看看任务对象定义:

class Task(object):
...
    def run(self):
        return self.target.send(self.sendval)

当第二次调用 task.run() 时,它会将它的 sendval 值(之前由 result.handle() 设置为它的 tid)发送到它的 .target - foo 协程。这是 foo 协程对象最终接收其 mytid 值的地方。

foo 协同程序对象 运行s 直到它的下一个 yield,沿途打印它的消息,并且 returns None (因为有yield 右侧没有任何内容)。 Nonetask.run() 方法的 return 值。

这不是 SystemCall 的一个实例,因此任务在第二次通过时不是 handled/scheduled。

其他邪恶的事情也可能发生,但这就是我现在看到的流程。