一种简单的用户态Python多任务(线程)方案

一种简单的用户态Python多任务(线程)方案


在Python界,我们常常使用多进程来实现并行加速。在各进程内部,再采用多 线程方案进行io调度,但这通常有一些问题,我们这里以传统的CPython实现为 准,该实现直接采用来操作系统原生线程实现多线程,这有一些问题,首先, 操作系统线程并不是免费的,它有自己的栈、内核数据结构,并且(通常)是基 本调度单元,这会带来一些并不算不重要的overhead,我们在这种场景下使用 多线程的目的本质上就是进行io调度,那么大部分的抢占对我们是没有意义的; 第二,Python实现中,每个线程执行一定条数的字节码后主动让出时间片,这 种调度时机并不是我们想要的。对于特定的应用,这可以用IO多路复用来解决, IO多路复用就是一个IO调度器,但是,这种方式不能提供一种透明的抽象,进 行IO的控制流必须显式得处理回调、事件等等,另外,我们能够同时进行的控 制流(任务)受到了不必要的限制——它本质上应该受限于我们处理IO的能力。

4.1 对控制流的抽象

我们用一个"闭包中的callable对象"来表达控制流中的某一状态,我们可以 得到这个对象,就得到了当前控制流,通过对该对象的调用恢复执行控制流。 大体上是这样:

def task(arg):
    def task_child(a):
        return some_func(arg, a)
    return task_child

对于一般的控制流的抽象,我们应该了解continuation,参考:

http://en.wikipedia.org/wiki/Continuation

4.2 设计

4.2.1 task

我们把满足下属条件的Python对象叫做task:

  1. 可以以某种确定的方式被调用,有0到多个入参
  2. 对该task的调用,应该返回
    1. Complete,结果
    2. Failed
    3. 一个list,该list由形如(head,tail1,tail2…)的元素组成,我们 把这种元素叫做task元祖

我们把满足下述条件的tuple叫做task元祖: 形如:(head, tail1, tail2…)

  1. head为一个task,且接受该tuple长度减1个参数
  2. tail1、tail2应该是:
    1. 一个Python对象
    2. 一个task元祖
  • IO task

    一个拥有触发条件的task叫做IO task.IO task只在满足触发条件的情况下 才会被调度。

4.3  实现

调度器维护一个task元组的队列,根据特定的调度策略选择task执行,执行的 结果将被投入该队列。IO task在满足触发条件的时候被调度,一种典型的IO task:

def recvsegment(s, length):
    rcv = s.recv(10000000)
    if len(rcv) == length:
        return rcv
    return lambda:rcv + recvsegment(s, length-len(rcv))

我们在每次调度的时候,会检查与一个IO task联系的标识符(文件描述符)是 否准备好,如果准备好,就执行该IO task.对于task元组中参数被求值完毕 (所有的tail都为一个值)的情况,我们会以这些参数为入参,执行head。在 这种设计下,任何IO操作应该和主控制流分离,以(head, io1,io2…)的方 式进行。

事实上,task的执行载体可以是原生线程,这样,就实现了M:N的并发模型, 对于特殊的情况(IO完成却没有机会被调度倒的task),还可以动态增减线程 进行处理。

在特定的假设下,这套多任务方案有一定的意义。这个假设就是:1.IO占据了 控制流的大部分时间。2.任何一种非IO操作都可以很快完成。因此,在特殊 的时机交出控制权,而不是抢占调度,是可以接受的。事实上,现实世界有 很多系统满足这样的要求。

Author: qingant <[email protected]>

Date: 2011-06-12 Sun

HTML generated by org-mode 6.33x in emacs 23

你可能感兴趣的:(一种简单的用户态Python多任务(线程)方案)