huangyi/2006-09-05.

今天邮件列表里讨论 python2.5 中增强的yield特性,讨论得热火朝天。

顿觉心血来潮,就用这增强型的 yield 大致模拟了下 StacklessPython 的 api。

虽然不能一模一样,不过感觉也只能做到这一步了。

主要是因为相对 StacklessPython 来说 yield 有这么几个限制:

  • 函数只能通过 yield 来挂起,这导致实现 channel 的时候只能通过 yield command(c.send, value)value = yield command(c.receive) 这样的语法来使当前 tasklet 挂起 (一定条件下) ,不像 stacklesspython 直接 some_channel.send(...) 就有可能挂起当前 tasklet 。

  • yield 只能向上一层,而不能直接 yield 到调用栈的最上层(或者甚至是指定 yield 到哪一层!!),这导致我们的 tasklet 只能在函数调用的第一层进行 yield 才能将执行权切换给调度程序,像下面:

   1 def a_task():
   2     a_func()
   3 def a_func():
   4     # 在这个函数里是没用办法将执行权切换给调度程序的。

不像 StacklessPython ,随便哪里调用 stackless.schedule() 都可以把执行权交出去。

谁叫人家c写的呢。:(

本来还想把 evolution 也转过来的,发现 pygame 还没为 windows 编译 python2.5 的版本,自己编译太麻烦,还是等 python2.5 正式发布了再说吧。

  • 注意:运行下面程序需要 python2.5

   1 #coding:utf-8
   2 from collections import deque
   3 
   4 '''
   5 http://www.stackless.com/
   6 '''
   7 
   8 debuglevel = 0
   9 
  10 readys = deque() # 就绪队列
  11 
  12 class command(object):
  13     def __init__(self, func, *args, **kw):
  14         self.func = func
  15         self.args = args
  16         self.kw = kw
  17 
  18     def __call__(self, task):
  19         return self.func(task, *self.args, **self.kw)
  20 
  21 class channel(object):
  22     ''' http://www.stackless.com/wiki/Channels '''
  23     def __init__(self):
  24         self.senders = deque() # 发送队列 元素:(tasklet, obj)
  25         self.receivers = deque() # 接收队列 元素:tasklet
  26 
  27     def send(self, sender, obj):
  28         '''向channel中发送数据,如果没用接受者,则让该tasklet等待'''
  29         if debuglevel:
  30             print 'tasklet:',sender,'send data:',obj,';receivers:',len(self.receivers)
  31         if self.receivers:
  32             receiver = self.receivers.popleft()
  33             ready(sender, None)
  34             run(receiver, obj)
  35         else:
  36             self.senders.append( (sender, obj) )
  37 
  38     def receive(self, receiver):
  39         ''' 从channel中接收数据,如果没用发送者,则让该tasklet等待'''
  40         if debuglevel:
  41             print 'tasklet:',receiver,'receive data ;senders:',len(self.senders)
  42         if self.senders:
  43             sender, obj = self.senders.popleft()
  44             ready(receiver,obj)
  45             run(sender, None)
  46         else:
  47             self.receivers.append(receiver)
  48 
  49 class tasklet(object):
  50     '''http://www.stackless.com/wiki/Tasklets'''
  51     def __init__(self, func):
  52         self.func = func
  53 
  54     def __call__(self, *arg, **kw):
  55         '''将 genarator 加到就绪队列'''
  56         ready(self.func(*arg, **kw), None)
  57 
  58 def run(task, obj):
  59     ''' 执行task '''
  60     try:
  61         result = task.send(obj)
  62     except StopIteration:
  63         pass
  64     else:
  65         if isinstance(result, command):
  66             # 给tasklet以执行 "系统命令" 的机会
  67             # 用户代码: yield command(func, ... )
  68             result(task)
  69         else:
  70             ready(task, None) # 加到就绪队列队尾,等待调度执行
  71 
  72 def ready(task, obj):
  73     ''' 加入就绪队列 等待调度 '''
  74     readys.append( (task, obj) )
  75 
  76 def schedule():
  77     ''' 调度就绪队列中的 tasklet '''
  78     while readys:
  79         task, obj = readys.popleft()
  80         run(task, obj)
  81 
  82 # ============ test ===========
  83 
  84 def simple_task(a):
  85     while True:
  86         print a
  87         yield None
  88 
  89 def receiver(id, c):
  90     value = yield command(c.receive)
  91     print id,'receive', value
  92     while value:
  93         value = yield command(c.receive)
  94         print id,'received', value
  95 
  96 def sender(id, c):
  97     value = 20
  98     while value:
  99         print id,'send',value
 100         yield command(c.send, value)
 101         value -= 1
 102 
 103 def test_simple():
 104     tasklet(simple_task)(0)
 105     tasklet(simple_task)(10)
 106     tasklet(simple_task)(100)
 107 
 108 def test_channel():
 109     c = channel()
 110     tasklet(receiver)('receiver1', c)
 111     tasklet(receiver)('receiver2', c)
 112     tasklet(sender)('sender1', c)
 113     c1 = channel()
 114     tasklet(receiver)('receiver3', c1)
 115     tasklet(sender)('sender2', c1)
 116 
 117 if __name__=='__main__':
 118     #test_simple()
 119     test_channel()
 120     schedule()