Archive for November, 2007

来自未来的erlang

Friday, November 30th, 2007

来自未来的erlang

erlang算是我认真学的第一门函数式编程语言。而他实在是带来了很多新的概念,预示着未来的发展方向。

对于函数式编程,我在使用Python时就已经开始使用了,只不过,依托Python极其垃圾的函数式编程实现,效率极低。所以除了用来摆酷,没什么实际用途。后来好友jorge也推荐过我学其他的函数式编程语言,如lisp、scheme、guile等等。终究因为没有对应的Windows版本而半途而废。我个人平时大部分时间都使用debian,只不过,我不希望我的程序难于部署。而我编程的成就感也是来自更多的人在使用。

而关于erlang,最早是我们头叫我去研究的。当时他很讨厌公司已有的软件开发和部署方式,也就是load balance+app*n的方式。而且公司常用的几种编程语言,Java、Python都无法利用多核CPU。PHP依赖Apache的多进程fork方式实现了利用多核CPU,但是无法利用数据库连接池等等问题也是很烦躁。于是,最后他发现了erlang这个东西,并且嘱咐我关注一下。

关于erlang的优点,网上有很多介绍,我就从我所关注的几个优点来讲。

1、函数式编程。函数式编程语言具有特有的简单性,尽管对于我们这类从过程式语言,并升级到面向对象语言的程序员来说,思想的转弯太大。但是如果转过来以后,却面对着桃花源一样的简单性。从效果上讲lambda算子与图灵机具有相同的作用,过程式语言可以完成的功能,用函数式语言一样可以实现。

2、并行计算。现代已有的过程式语言和面向对象语言在并行计算方面都有很恶心的复杂性。虽说简单的讲有线程、异步、协程等几种方式,但是并发控制却是大多数程序员心中的噩梦。而同时又要面对神出鬼没的死锁。

3、集群计算。这个变态的时代,我们都很无奈,当几千个并发(仅仅是短连接)向你涌来时,除了加机器还能有什么办法。但是加机器就有个艺术的问题了。load balance是不可或缺的,但是lb的价格足以让任何级别的公司心痛。而erlang内置的集群支持则可以平衡的将负载分配到各个erlang服务器上。

4、错误处理。当然,即便是连C这类的语言其实也是有错误处理的,但是错误处理有个层次的问题。如果你满足于函数调用返回错误码,那就算了。但是问题是错误的发生可能比你想象的更加离奇。也许是服务器的电源线不小心被踢掉了;也许是你新部署的应用被运维的同事当木马给杀掉了;甚至仅仅是因为你没有交机房的网费而被拔掉了网线。而在这时,你都要自己写程序来监控么,噩梦,绝对的噩梦。还好,这个世界上有erlang这个东西,确保你可以在多台计算机上分别部署应用并互相监控。并且在某台服务器出错时,不至于让整个应用挂掉。

其实每一种成功的编程语言都有其最成功的应用领域,面对不断扩大的服务器规模、多核CPU、CPU频率的制约,erlang将是未来相当一段时间的成功者。

基于memcache的队列设计

Wednesday, November 28th, 2007

基于memcache的队列设计

作者: gashero

1   需求

两个系统交互,使用memcache作为中间的接口,因为传送的数据可能类型很多,而且无法定义一个很好的键名,所以设计了memcache队列,以供数据传输用。

2   基本设计原理

memcache中实现了命令 incrdecr 分别对一个键进行自加和自减操作。执行命令的结果是返回自加或自减后的结果。因为是通用memcache命令,所以在各种语言的接口中都有实现,本例探讨基于Python的实现。

协议接口:

incr <key> <value>\r\n

decr <key> <value>\r\n

Python接口:

incr(key,delta=1)

decr(key,delta=1)

3   队列结构设计

基于memcache实现的队列,需要实现两个基本变量以控制整个队列。

3.1   队列指针

队列指针有两个变量,一个是 head 另一个是 tail 分别为队列的最后一个ID和最前一个ID。每次加入新的item都会使得 tail++ ,而每次取走一个item都会使得 head++

3.2   队列元素

每个队列元素都是按照 prefix+index 形式构造,即一个类别前缀加上索引数字。之后在value中存储串行化以后的数据。

3.3   队列的循环

经测试memcache中的自增变量的数据类型为long型,即有符号32位整数。最大可取值区间为-2199023255552至 2199023255551。所以,为了防止系统长时间运行出现问题,必须设置一个队列的循环机制。无论head还是tail变量,必须设计一个最大值, 当达到最大值时就清零循环。

经测试,在Python调用memcache测试最大允许自增变量值超过最大可取值范围时,发生了ValueError异常。而在memcache 协议中则表示根本没有验证数据溢出的情况。在应用中需要酌情考虑。比如设置为1048576,为队列的最大长度,并且每次在index达到这个数字时即循 环。

4   需要注意的问题

4.1   数据项丢失

memcache的设计目标是提供高速度的数据缓存服务,并不确保数据的完整性,在内存不足时,memcache甚至会主动删除一些存储的键值。所以,即便是通过正常的接口、head与tail指定的标志,获取键值时也要考虑到异常的可能。

4.2   及时删除过期变量

在接收端应用程序取走数据之后应该立即删除memcache中的对应键,否则时间长了可能会因为占用内存过多而影响尚未取走变量的存储。

nagios的插件体系

Tuesday, November 27th, 2007

nagios的插件体系

nagios的插件体系是很方便而友好的,可以允许用任何的程序编写插件,并与nagios很好的协同工作。

nagios的插件实际上就是一个单独的应用程序,所以几乎可以说毫无限制,你可以写一个shell脚本,也可以用汇编写一个内存访问程序,这些都是随便的。当然,需要少许的配置让nagios找到你的这个程序。

传入参数方面,nagios允许使用命令行的那些传入参数,貌似也可以用环境变量来传递。

输出参数方面分为两部分,一个是应用程序调用时的返回代码。一般来说返回码用来标识程序的运行结果,0表示成功。但是nagios却按照这个返回码来显示一个监控参数的危险级别,这是个很好的创意。同样,一个程序运行时的返回码为0则表示这个参数的检测没有出错,如果返回了一个正数,则数字越大表示越危险。在nagios的界面中也会按照不同的颜色来显示。

另外一个输出就是标准输出了,也就是print到屏幕上的内容,nagios会在调用时自动修改重定向以接收这些输出。这些内容nagios并不做分析,而是全部保存,以备了解报警的详细信息。

nagios的插件体系算是我这些年接触的多种插件体系当中最灵活的了,以后也会尽可能的参考他来做。

如何嵌入pyrex

Tuesday, November 27th, 2007

 

如何嵌入pyrex

译者: gashero

 

简介

对于大多数Py程序员来说,pyrex已经是一个实现Python/C包装的成功解决方案,他消除了所有Python/C之间的杂七杂八的问题。

但是大部分的pyrex使用都是集中于构建扩展类型和模块,使得他们可以象一般的Python模块一样的使用。

这个小教程会引导你制作单独的可执行程序,其实是将pyrex嵌入了Python解释器来运行。

 

嵌入Python?怎么做到的?为什么这么做?

现代,操作系统、编程语言和开发工具都成为了一种信仰,Python也无法逃脱。

如果你尝试嵌入,那么会有一大堆Python程序员嘲笑你,他们更推崇扩展,而不是嵌入。

在大多数情况下,他们是对的。而且大多数时候,扩展Python也比嵌入Python到C程序更有意义。

但是仍然有一些情况嵌入是更方便的,例如:

  • 一个程序需要711权限(u+rwx,go-rw+x)
  • 一个程序需要在ps和top命令中按照名称显示,而不是python
  • 懒得区别用户的Python版本
  • 适合你的其他理由

 

这个教程会谈到哪些东西?

这个文档中会介绍使用pyrex构建单独的可执行程序的过程,或者说看起来像一个可执行程序,当然运行时会动态的链接libpython2.x.so文件。

 

处方

 

创建一个简单的.pyx文件

如下代码是完整的pyrex源码,可以直接编译成可执行文件:

"""

实验通过pyrex嵌入Python

"""

#从C头文件中获取我们需要的功能

cdef extern from "stdio.h":

    int printf(char* format,...)

cdef extern from "Python.h":

    #嵌入函数

    void Py_Initialize()

    void Py_Finalize()

    void PySys_SetArgv(int argc, char** argv)

    #声明其他的Python/C接口函数

    void Py_INCREF(object o)

    void Py_DECREF(object o)

    object PyString_FromStringAndSize(char *, int)

#注意:必须声明函数原型 'init<mymodulename>()' 而mymodulename就是这段

#代码的文件名,比如本例就叫做testpyx.pyx

cdef public void inittestpyx()

#这里可以定义一系列的Python扩展类型、类、方法等

cdef class Testclass:

    cdef public int someint

    cdef public char* somestring    def __init__(self):

        self.someint=43

        self.somestring="this is a string"

def hello(self):

        print "Hello, this is an instance of %s"%self.__class__.__name__

#现在可以声明C的main()函数了

cdef public int main(int argc, char** argv):

    Py_Initialize()

    PySys_SetArgv(argc,argv)

    printf("initialising testpyx\n")

    inittestpyx()

    #初始化完成,可以做Python的功能了

    printf("testmain: instantiating Testclass\n")

    testobj=Testclass()

    printf("testmain: created testobj\n")

print "testobj.someint=%s"%testobj.someint

    print "testobj.somestring=%s"%testobj.somestring

print "calling testobj.hello()"

    testobj.hello()

    #离开程序之前释放资源

    print "cleaning up"

    Py_Finalize()

 

编写Makefile

如下的Makefile对我工作的很好,希望对你有用:

#创建单独的Pyrex程序

PYVERSION=2.3

PYPREFIX=/usr

INCLUDES=-i$(PYPREFIX)/include/python$(PYVERSION)testpyx: testpyx.o

    gcc -o $@ $^ -lpython$(PYVERSION)

testpyx.o: testpyx.c

    gcc -c $^ $(INCLUDES)

testpyx.c: testpyx.pyx

    pyrexc testpyx.pyx

 

总结

想要做可以单独工作的pyrex可执行程序,必须要:

  1. 导入必须的几个API: Py_Initialize()Py_Finalize()PySys_SetArgv() ,还有就是你需要调用的其他函数

  2. 必须明确的声明pyrex生成的模块初始化函数的C原型,如本例的’inittestpyx’。

  3. 创建一个C可以调用的’main()’函数,需要注意:

    1. 初始化Python解释器
    2. 调用pyrex生成的模块初始化函数
    3. 随程序需要随便写Python语句
    4. 停止Python解释器
  4. 创建一个Makefile需要:

    1. 明确的编译.pyx到.c文件,使用单独的命令 pyrexc
    2. 编译生成的.c文件到.o或.obj文件,确保提供正确的include路径
    3. 链接结果到最终的可执行文件,并确保它可以找到Python运行时库

web.py的HTTP研究

Monday, November 26th, 2007

 

web.py的HTTP研究

作者: gashero 日期: 2007-11-21 版本: web.py-0.22

 

目录

1   简介

我想要实现使用twisted作为HTTP服务器,而web.py作为后端的动态引擎的部署方式。从而实现高性能,又易于学习的一种开发方式。

2   一个基本的例子

见web.py首页上提供的例子,如下:

import web
urls=(
    '/(.*)','hello',
)
class hello:
    def GET(self,name):
        i=web.input(times=1)
        if not name:
            name='world'
        for c in xrange(int(i.times)):
            print 'Hello,',name+'!'
if __name__=="__main__":
    web.run(urls,globals())

可见一个web.py编写的应用包含一个url映射表,一些映射处理类,类按照请求方法命名的方法实现了对应的逻辑,最后包含一个启动服务器的语句,只要传入URL映射即可。

3   web.py所属文件简介

3.1   __init__.py

导入所有的下属模块,并且有个主函数会运行doctest测试用例。导入的模块全部分为两种,例如:

import utils
from utils import *

3.2   cheetah.py

98行代码实现了cheetah的接口规范化,最终还是要导入真正的Cheetah,只是这里提供的接口比较好用。如:

def render(template,terms=None,asTemplate=False,base=None,isString=False)

3.3   db.py

自动导入DBUtils作为数据库连接池并提供web.py友好的接口。有703行代码,貌似大了一点。

3.4   debugerror.py

以Django的模板提供了调试服务器运行时的错误信息展示。316行代码。

3.5   form.py

提供了简单的表单代码生成,和貌似有数据验证功能。提供了面向对象的组织结构。215行代码的。

3.6   http.py

269行代码。实现了HTTP的一些机制,比如cookie、缓存过期、重定向等等。好像是与httpserver.py没有什么太大的耦合,但是也要小心,看到一些web.header之类的设置,这岂不是全局的?

3.7   httpserver.py

224行代码。实现了一个简单的HTTP服务器。还是基于Python自带的那个SimpleHTTPServer模块写的,所以也是单线程的。这里一共就2个函数,分别是启动不同的服务器,不过貌似都是使用wsgi方式实现的。

3.8   net.py

155行代码。IP地址验证,URL验证等等。

3.9   request.py

153行代码。好像就是请求处理的入口了。同时这里也有个 run() 函数,貌似就是启动服务器的入口。

3.10   template.py

878行代码,貌似实现了一个比较像Cheetah的模板系统吧,里面有明确的代码显示,这里面做了很多代码解析的工作,至少是有词法解析器。

3.11   utils.py

796行代码。看来就是我以前一直期望的那个具有很多超牛功能的东西,如函数的执行缓存等等。

3.12   webapi.py

369行代码。提供了一些常用功能的函数接口,如 badrequest() 、 notfound() 、 gone() 等等,这样可以加快解决过程。另外像 setcookie() 、 cookies() 、 debug() 等等也是编程所必需的。

3.13   wsgi.py

54行代码。提供了3个函数,分别是 runfcgi() 、 runscgi() 、 runwsgi() 。分别提供3种不同的发布方式。

3.14   wsgiserver/

仅内含一个 __init__.py 文件,其实是从CherryPy搞来的WSGI服务器的代码。

4   HTTP处理流程

4.1   启动服务器

启动服务器的代码:

web.run(urls,globals())

实际上是调用 web.request 模块中的 run() 函数。代码如下:

def run(inp,fvars,*middleware):
    autoreload=http.reloader in middleware
    return wsgi.runwsgi(webapi.wsgifunc(webpyfunc(inp,fvars,autoreload),*middleware))

其中首先确定是否需要动态重新装入功能,然后就是把 webpyfunc() 的调用结果,就是一个函数对象传入 webapi.wsgifunc() 函数。 webapi.wsgifunc() 函数接受两个参数,一个是 webpyfunc() 的结果,另一个是中间件列表。

4.2   webpyfunc() 生成调用请求处理器的函数

在模块 web.request 中,声明如下:

def webpyfunc(inp,fvars,autoreload=False)

参数解释:

  • inp :可以是函数或一个tuple,用于URL映射
  • fvars :一个dict,存储可用的变量
  • autoreload :是否自动重装

完全按照例子当中执行时webpyfunc()函数的调用参数为:

webpyfunc(urls,globals(),False)

这样最终是返回的结果是:

return lambda: handle(urls,globals())

即便 autoreload=True 估计也是同样的结果,只是可以自动重新加载而已。

5   启动的HTTP服务器

经分析,默认的服务器最终启动的是 web.wsgi 模块中 runwsgi() 函数中的如下语句:

return httpserver.runsimple(func,validip(listget(sys.argv,1,'')))

这个函数实际上内部居然还包含两个类的定义,一个作为静态文件服务的,叫 StaticApp ,另外一个用作动态的,叫 WSGIWrapper

6   总结

真不知道wsgi为什么这么重要,以至于我接触的几个框架基本上都是用wsgi来实现底层。每每让我无法下手修改其行为。django算是走的距离核心最近的一个了,而其他的基本上还没有摸到边。

总之,还是自己写那个htmid吧,自己写的终归了解。

 

Stackless Python并发式编程介绍[已校对版]

Sunday, November 18th, 2007

Stackless Python并发式编程介绍

作者: Grant Olson

作者:    Grant Olson
电子邮件:    olsongt@verizon.net
日期:    2006-07-07
译者:    刘禹呈
电子邮件:    lych77@gmail.com
日期:    2007-09-19
校对:    gashero
电子邮件:    harry.python@gmail.com
日期:    2007-09-20
原文地址:    http://members.verizon.net/olsongt/stackless/why_stackless.html
目录

1 介绍

1.1 为什么要使用Stackless

摘自 stackless 网站。

Note

Stackless Python 是Python编程语言的一个增强版本,它使程序员从基于线程的编程方式中获得好处,并避免传统线程所带来的性能与复杂度问题。Stackless为 Python带来的微线程扩展,是一种低开销、轻量级的便利工具,如果使用得当,可以获益如下:

  • 改进程序结构
  • 增进代码可读性
  • 提高编程人员生产力

以上是Stackless Python很简明的释义,但其对我们意义何在?——就在于Stackless提供的并发建模工具,比目前其它大多数传统编程语言所提供的,都更加易用: 不仅是Python自身,也包括Java、C++,以及其它。尽管还有其他一些语言提供并发特性,可它们要么是主要用于学术研究的(如 Mozart/Oz),要么是罕为使用、或用于特殊目的的专业语言(如Erlang)。而使用stackless,你将会在Python本身的所有优势之 上,在一个(但愿)你已经很熟悉的环境中,再获得并发的特性。

这自然引出了个问题:为什么要并发?

1.1.1 现实世界就是并发的

现实世界就是“并发”的,它是由一群事物(或“演员”)所组成,而这些事物以一种对彼此所知有限的、松散耦合的方式相互作用。传说中面向对象编程有 一个好处,就是对象能够对现实的世界进行模拟。这在一定程度上是正确的,面向对象编程很好地模拟了对象个体,但对于这些对象个体之间的交互,却无法以一种 理想的方式来表现。例如,如下代码实例,有什么问题?

def familyTacoNight():
    husband.eat(dinner)
    wife.eat(dinner)
    son.eat(dinner)
    daughter.eat(dinner)

第一印象,没问题。但是,上例中存在一个微妙的安排:所有事件是次序发生的,即:直到丈夫吃完饭,妻子才开始吃;儿子则一直等到母亲吃完才吃;而女 儿则是最后一个。在现实世界中,哪怕是丈夫还堵车在路上,妻子、儿子和女儿仍然可以该吃就吃,而要在上例中的话,他们只能饿死了——甚至更糟:永远没有人 会知道这件事,因为他们永远不会有机会抛出一个异常来通知这个世界!

1.1.2 并发可能是(仅仅可能是)下一个重要的编程范式

我个人相信,并发将是软件世界里的下一个重要范式。随着程序变得更加复杂和耗费资源,我们已经不能指望摩尔定律来每年给我们提供更快的CPU了,当 前,日常使用的个人计算机的性能提升来自于多核与多CPU机。一旦单个CPU的性能达到极限,软件开发者们将不得不转向分布式模型,靠多台计算机的互相协 作来建立强大的应用(想想GooglePlex)。为了取得多核机和分布式编程的优势,并发将很快成为做事情的方式的事实标准。

1.2 安装stackless

安装Stackless的细节可以在其网站上找到。现在Linux用户可以通过SubVersion取得源代码并编译;而对于Windows用户, 则有一个.zip文件供使用,需要将其解压到现有的Python安装目录中。接下来,本教程假设Stackless Python已经安装好了,可以工作,并且假设你对Python语言本身有基本的了解。

2 stackless起步

本章简要介绍了 stackless 的基本概念,后面章节将基于这些基础,来展示更加实用的功能。

2.1 微进程(tasklet)

微进程是stackless的基本构成单元,你可以通过提供任一个Python可调用对象(通常为函数或类的方法)来建立它,这将建立一个微进程并将其添加到调度器。这是一个快速演示:

Python 2.4.3 Stackless 3.1b3 060504 (#69, May  3 2006, 19:20:41) [MSC v.1310 32
bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import stackless
>>>
>>> def print_x(x):
...     print x
...
>>> stackless.tasklet(print_x)('one')
<stackless.tasklet object at 0x00A45870>
>>> stackless.tasklet(print_x)('two')
<stackless.tasklet object at 0x00A45A30>
>>> stackless.tasklet(print_x)('three')
<stackless.tasklet object at 0x00A45AB0>
>>>
>>> stackless.run()
one
two
three
>>>

注意,微进程将排起队来,并不运行,直到调用 stackless.run()

2.2 调度器(scheduler)

调度器控制各个微进程运行的顺序。如果刚刚建立了一组微进程,它们将按照建立的顺序来执行。在现实中,一般会建立一组可以再次被调度的微进程,好让每个都有轮次机会。一个快速演示:

Python 2.4.3 Stackless 3.1b3 060504 (#69, May  3 2006, 19:20:41) [MSC v.1310 32
bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import stackless
>>>
>>> def print_three_times(x):
...     print "1:", x
...     stackless.schedule()
...     print "2:", x
...     stackless.schedule()
...     print "3:", x
...     stackless.schedule()
...
>>>
>>> stackless.tasklet(print_three_times)('first')
<stackless.tasklet object at 0x00A45870>
>>> stackless.tasklet(print_three_times)('second')
<stackless.tasklet object at 0x00A45A30>
>>> stackless.tasklet(print_three_times)('third')
<stackless.tasklet object at 0x00A45AB0>
>>>
>>> stackless.run()
1: first
1: second
1: third
2: first
2: second
2: third
3: first
3: second
3: third
>>>

注意:当调用 stackless.schedule() 的时候,当前活动微进程将暂停执行,并将自身重新插入到调度器队列的末尾,好让下一个微进程被执行。一旦在它前面的所有其他微进程都运行过了,它将从上次 停止的地方继续开始运行。这个过程会持续,直到所有的活动微进程都完成了运行过程。这就是使用stackless达到合作式多任务的方式。

2.3 通道(channel)

通道使得微进程之间的信息传递成为可能。它做到了两件事:

  1. 能够在微进程之间交换信息。
  2. 能够控制运行的流程。

又一个快速演示:

C:>c:python24python
Python 2.4.3 Stackless 3.1b3 060504 (#69, May  3 2006, 19:20:41) [MSC v.1310 32
bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import stackless
>>>
>>> channel = stackless.channel()
>>>
>>> def receiving_tasklet():
...     print "Recieving tasklet started"
...     print channel.receive()
...     print "Receiving tasklet finished"
...
>>> def sending_tasklet():
...     print "Sending tasklet started"
...     channel.send("send from sending_tasklet")
...     print "sending tasklet finished"
...
>>> def another_tasklet():
...     print "Just another tasklet in the scheduler"
...
>>> stackless.tasklet(receiving_tasklet)()
<stackless.tasklet object at 0x00A45B30>
>>> stackless.tasklet(sending_tasklet)()
<stackless.tasklet object at 0x00A45B70>
>>> stackless.tasklet(another_tasklet)()
<stackless.tasklet object at 0x00A45BF0>
>>>
>>> stackless.run()
Recieving tasklet started
Sending tasklet started
send from sending_tasklet
Receiving tasklet finished
Just another tasklet in the scheduler
sending tasklet finished
>>>

接收的微进程调用 channel.receive() 的时候,便阻塞住,这意味着该微进程暂停执行,直到有信息从这个通道送过来。除了往这个通道发送信息以外,没有其他任何方式可以让这个微进程恢复运行。

若有其他微进程向这个通道发送了信息,则不管当前的调度到了哪里,这个接收的微进程都立即恢复执行;而发送信息的微进程则被转移到调度列表的末尾,就像调用了 stackless.schedule() 一样。

同样注意,发送信息的时候,若当时没有微进程正在这个通道上接收,也会使当前微进程阻塞:

>>> stackless.tasklet(sending_tasklet)()
<stackless.tasklet object at 0x00A45B70>
>>> stackless.tasklet(another_tasklet)()
<stackless.tasklet object at 0x00A45BF0>
>>>
>>> stackless.run()
Sending tasklet started
Just another tasklet in the scheduler
>>>
>>> stackless.tasklet(another_tasklet)()
<stackless.tasklet object at 0x00A45B30>
>>> stackless.run()
Just another tasklet in the scheduler
>>>
>>> #Finally adding the receiving tasklet
...
>>> stackless.tasklet(receiving_tasklet)()
<stackless.tasklet object at 0x00A45BF0>
>>>
>>> stackless.run()
Recieving tasklet started
send from sending_tasklet
Receiving tasklet finished
sending tasklet finished

发送信息的微进程,只有在成功地将数据发送到了另一个微进程之后,才会重新被插入到调度器中。

2.4 总结

以上涵盖了stackless的大部分功能。似乎不多是吧?——我们只使用了少许对象,和大约四五个函数调用,来进行操作。但是,使用这种简单的API作为基本建造单元,我们可以开始做一些真正有趣的事情。

3 协程(coroutine)

3.1 子例程的问题

大多数传统编程语言具有子例程的概念。一个子例程被另一个例程(可能还是其它某个例程的子例程)所调用,或返回一个结果,或不返回结果。从定义上说,一个子例程是从属于其调用者的。

见下例:

def ping():
    print "PING"
    pong()

def pong():
    print "PONG"
    ping()

ping()

有经验的编程者会看到这个程序的问题所在:它导致了堆栈溢出。如果运行这个程序,它将显示一大堆讨厌的跟踪信息,来指出堆栈空间已经耗尽。

3.1.1 堆栈

我仔细考虑了,自己对C语言堆栈的细节究竟了解多少,最终还是决定完全不去讲它。似乎,其他人对其所尝试的描述,以及图表,只有本身已经理解了的人才能看得懂。我将试着给出一个最简单的说明,而对其有更多兴趣的读者可以从网上查找更多信息。

每当一个子例程被调用,都有一个“栈帧”被建立,这是用来保存变量,以及其他子例程局部信息的区域。于是,当你调用 ping() ,则有一个栈帧被建立,来保存这次调用相关的信息。简言之,这个帧记载着 ping 被调用了。当再调用 pong() ,则又建立了一个栈帧,记载着 pong 也被调用了。这些栈帧是串联在一起的,每个子例程调用都是其中的一环。就这样,堆栈中显示: ping 被调用所以 pong 接下来被调用。显然,当 pong() 再调用 ping() ,则使堆栈再扩展。下面是个直观的表示:

堆栈
1 ping 被调用
2 ping 被调用,所以 pong 被调用
3 ping 被调用,所以 pong 被调用,所以 ping 被调用
4 ping 被调用,所以 pong 被调用,所以 ping 被调用,所以 pong 被调用
5 ping 被调用,所以 pong 被调用,所以 ping 被调用,所以 pong 被调用,所以 ping 被调用
6 ping 被调用,所以 pong 被调用,所以 ping 被调用,所以 pong 被调用,所以 ping 被调用……

现在假设,这个页面的宽度就表示系统为堆栈所分配的全部内存空间,当其顶到页面的边缘的时候,将会发生溢出,系统内存耗尽,即术语“堆栈溢出”。

3.1.2 那么,为什么要使用堆栈?

上例是有意设计的,用来体现堆栈的问题所在。在大多数情况下,当每个子例程返回的时候,其栈帧将被清除掉,就是说堆栈将会自行实现清理过程。这一般 来说是件好事,在C语言中,堆栈就是一个不需要编程者来手动进行内存管理的区域。很幸运,Python程序员也不需要直接来担心内存管理与堆栈。但是由于 Python解释器本身也是用C实现的,那些实现者们可是需要担心这个的。使用堆栈是会使事情方便,除非我们开始调用那种从不返回的函数,如上例中的,那 时候,堆栈的表现就开始和程序员别扭起来,并耗尽可用的内存。

3.2 走进协程

此时,将堆栈弄溢出是有点愚蠢的。 ping() 和 pong() 本不是真正意义的子例程,因为其中哪个也不从属于另一个,它们是“协程”,处于同等的地位,并可以彼此间进行无缝通信。

堆栈
1 ping 被调用
2 pong 被调用
3 ping 被调用
4 pong 被调用
5 ping 被调用
6 pong 被调用

在stackless中,我们使用通道来建立协程。还记得吗,通道所带来的两个好处中的一个,就是能够控制微进程之间运行的流程。使用通道,我们可以在 ping 和 pong 这两个协程之间自由来回,要多少次就多少次,都不会堆栈溢出:

#
# pingpong_stackless.py
#

import stackless

ping_channel = stackless.channel()
pong_channel = stackless.channel()

def ping():
    while ping_channel.receive(): #在此阻塞
        print "PING"
        pong_channel.send("from ping")

def pong():
    while pong_channel.receive():
        print "PONG"
        ping_channel.send("from pong")

stackless.tasklet(ping)()
stackless.tasklet(pong)()

# 我们需要发送一个消息来初始化这个游戏的状态
# 否则,两个微进程都会阻塞
stackless.tasklet(ping_channel.send)('startup')

stackless.run()

你可以运行这个程序要多久有多久,它都不会崩溃,且如果你检查其内存使用量(使用Windows的任务管理器或Linux的top命令),将会发现 使用量是恒定的。这个程序的协程版本,不管运行一分钟还是一天,使用的内存都是一样的。而如果你检查原先那个递归版本的内存用量,则会发现其迅速增长,直 到崩溃。

3.3 总结

是否还记得,先前我提到过,那个代码的递归版本,有经验的程序员会一眼看出毛病。但老实说,这里面并没有什么“计算机科学”方面的原因在阻碍它的正 常工作,有些让人坚信的东西,其实只是个与实现细节有关的小问题——只因为大多数传统编程语言都使用堆栈。某种意义上说,有经验的程序员都是被洗了脑,从 而相信这是个可以接受的问题。而stackless,则真正察觉了这个问题,并除掉了它。

4 轻量级线程

与当今的操作系统中内建的、和标准Python代码中所支持的普通线程相比,“微线程”要更为轻量级,正如其名称所暗示。它比传统线程占用更少的内存,并且微线程之间的切换,要比传统线程之间的切换更加节省资源。

为了准确说明微线程的效率究竟比传统线程高多少,我们用两者来写同一个程序。

4.1 hackysack模拟

Hackysack是一种游戏,就是一伙脏乎乎的小子围成一个圈,来回踢一个装满了豆粒的沙包,目标是不让这个沙包落地,当传球给别人的时候,可以耍各种把戏。踢沙包只可以用脚。

在我们的简易模拟中,我们假设一旦游戏开始,圈里人数就是恒定的,并且每个人都是如此厉害,以至于如果允许的话,这个游戏可以永远停不下来。

4.2 游戏的传统线程版本

import thread
import random
import sys
import Queue

class hackysacker:
    counter = 0
    def __init__(self,name,circle):
        self.name = name
        self.circle = circle
        circle.append(self)
        self.messageQueue = Queue.Queue()

        thread.start_new_thread(self.messageLoop,())

    def incrementCounter(self):
        hackysacker.counter += 1
        if hackysacker.counter >= turns:
            while self.circle:
                hs = self.circle.pop()
                if hs is not self:
                    hs.messageQueue.put('exit')
            sys.exit()

    def messageLoop(self):
        while 1:
            message = self.messageQueue.get()
            if message == "exit":
                debugPrint("%s is going home" % self.name)
                sys.exit()
            debugPrint("%s got hackeysack from %s" % (self.name, message.name))
            kickTo = self.circle[random.randint(0,len(self.circle)-1)]
            debugPrint("%s kicking hackeysack to %s" % (self.name, kickTo.name))
            self.incrementCounter()
            kickTo.messageQueue.put(self)

def debugPrint(x):
    if debug:
        print x

debug=1
hackysackers=5
turns = 5

def runit(hs=10,ts=10,dbg=1):
    global hackysackers,turns,debug
    hackysackers = hs
    turns = ts
    debug = dbg

    hackysacker.counter= 0
    circle = []
    one = hackysacker('1',circle)

    for i in range(hackysackers):
        hackysacker(`i`,circle)

    one.messageQueue.put(one)

    try:
        while circle:
            pass
    except:
        #有时我们在清理过程中会遇到诡异的错误。
        pass

if __name__ == "__main__":
    runit(dbg=1)

一个“玩者”类的初始化用到了其名字,和一个指向包含了所有玩者的全局列表 circle 的引用,还有一个继承自Python标准库中的Queue类的消息队列。

Queue这个类的作用,与stackless的通道类似。它包含 put() 和 get() 方法,在一个空的Queue上调用 put() 会阻塞,直到另一个线程调用 put() 将数据送入Queue中为止。Queue这个类被设计为能与操作系统级的线程高效合作。

__init__ 方法接下来使用Python标准库中的thread模块新建一个线程,并在新线程中开始了一个消息循环。此消息循环是个无限循环,不停地处理队列中的消息。如果其收到一个特殊的消息 ‘exit’ ,则结束这个线程。

如果收到了另一个消息——指定其收到了沙包,玩者则从圈中随机选取一个其他玩者,通过向其发送一条消息来指定,将沙包再踢给它。

由类成员变量 hackysacker.counter 进行计数,当沙包被踢够了指定的次数时,将会向圈中的所有玩者都发送一条特殊的 ‘exit’ 消息。

注意,当全局变量debug为非零的时候,还有个函数debugPrint可以输出信息。我们可以使这游戏输出到标准输出,但当计时的时候,这会影响精确度。

我们来运行这个程序,并检查其是否正常工作:

C:Documents and SettingsgrantDesktopwhy_stacklesscode>c:python24python.exe
hackysackthreaded.py

1 got hackeysack from 1
1 kicking hackeysack to 4
4 got hackeysack from 1
4 kicking hackeysack to 0
0 got hackeysack from 4
0 kicking hackeysack to 1
1 got hackeysack from 0
1 kicking hackeysack to 3
3 got hackeysack from 1
3 kicking hackeysack to 3
4 is going home
2 is going home
1 is going home
0 is going home
1 is going home

C:Documents and SettingsgrantDesktopwhy_stacklesscode>

如我们所见,所有玩者到了一起,并很快地进行了一场游戏。现在,我们对若干次实验运行过程进行计时。Python标准库中有一个 timeit.py 程序,可以用作此目的。那么,我们也同时关掉调试输出:

C:Documents and SettingsgrantDesktopwhy_stacklesscode>c:python24python.exe c:Python24libtimeit.py -s "import hackysackthreaded" hackysackthreaded.runit(10,1000,0)
10 loops, best of 3: 183 msec per loop

在我的机器上,十个玩者共进行1000次传球,共使用了183毫秒。我们来增加玩者的数量:

C:Documents and SettingsgrantDesktopwhy_stacklesscode>c:python24python.exe c:Python24libtimeit.py -s "import hackeysackthreaded" hackeysackthreaded.runit(100,1000,0)
10 loops, best of 3: 231 msec per loop

C:Documents and SettingsgrantDesktopwhy_stacklesscode>c:python24python.exe c:Python24libtimeit.py -s "import hackysackthreaded" hackysackthreaded.runit(1000,1000,0)
10 loops, best of 3: 681 msec per loop

C:Documents and SettingsgrantDesktopwhy_stacklesscode>c:python24python.exe c:Python24libtimeit.py -s "import hackysackthreaded" hackysackthreaded.runit(10000,1000,0)
Traceback (most recent call last):
  File "c:Python24libtimeit.py", line 255, in main
    x = t.timeit(number)
  File "c:Python24libtimeit.py", line 161, in timeit
    timing = self.inner(it, self.timer)
  File "<timeit-src>", line 6, in inner
  File ".hackeysackthreaded.py", line 58, in runit
    hackysacker(`i`,circle)
  File ".hackeysackthreaded.py", line 14, in __init__
    thread.start_new_thread(self.messageLoop,())
error: can't start new thread

在我的3GHz、1G内存的机器上,当尝试10,000个线程的时候出现了错误。就不想拿出这详细的输出内容来扰人了,只是通过若干实验与出错过程 得出,在我机器上,此程序从1100个线程左右开始出错。另请注意,1000个线程时候所耗用的时间,是10个线程时候的大约三倍。

4.3 stackless

import stackless
import random
import sys

class hackysacker:
    counter = 0
    def __init__(self,name,circle):
        self.name = name
        self.circle = circle
        circle.append(self)
        self.channel = stackless.channel()

        stackless.tasklet(self.messageLoop)()

    def incrementCounter(self):
        hackysacker.counter += 1
        if hackysacker.counter >= turns:
            while self.circle:
                self.circle.pop().channel.send('exit')

    def messageLoop(self):
        while 1:
            message = self.channel.receive()
            if message == 'exit':
                return
            debugPrint("%s got hackeysack from %s" % (self.name, message.name))
            kickTo = self.circle[random.randint(0,len(self.circle)-1)]
            while kickTo is self:
                kickTo = self.circle[random.randint(0,len(self.circle)-1)]
            debugPrint("%s kicking hackeysack to %s" % (self.name, kickTo.name))
            self.incrementCounter()
            kickTo.channel.send(self)

def debugPrint(x):
    if debug:print x

debug = 5
hackysackers = 5
turns = 1

def runit(hs=5,ts=5,dbg=1):
    global hackysackers,turns,debug
    hackysackers = hs
    turns = ts
    debug = dbg

    hackysacker.counter = 0
    circle = []
    one = hackysacker('1',circle)

    for i in range(hackysackers):
        hackysacker(`i`,circle)

    one.channel.send(one)

    try:
        stackless.run()
    except TaskletExit:
        pass

if __name__ == "__main__":
    runit()

以上代码实质上与线程版本是等价的,主要区别仅在于我们使用微进程来代替线程,并且使用通道代替Queue来进行切换。让我们运行它,并检查输出:

C:Documents and SettingsgrantDesktopwhy_stacklesscode>c:Python24python.exe hackysackstackless.py
1 got hackeysack from 1
1 kicking hackeysack to 1
1 got hackeysack from 1
1 kicking hackeysack to 4
4 got hackeysack from 1
4 kicking hackeysack to 1
1 got hackeysack from 4
1 kicking hackeysack to 4
4 got hackeysack from 1
4 kicking hackeysack to 0

工作情况确如所料。现在来计时:

C:Documents and SettingsgrantDesktopwhy_stacklesscode>c:Python24python.exe c:Python24libtimeit.py -s"import hackysackstackless" hackysackstackless.runit(10,1000,0)
100 loops, best of 3: 19.7 msec per loop

其仅用了19.7毫秒,速度几乎是线程版本的10倍。现在我们同样开始增加微线程的数量:

C:Documents and SettingsgrantDesktopwhy_stacklesscode>c:Python24python.exe c:Python24libtimeit.py -s"import hackysackstackless" hackysackstackless.runit(100,1000,0)
100 loops, best of 3: 19.7 msec per loop

C:Documents and SettingsgrantDesktopwhy_stacklesscode>c:Python24python.exe c:Python24libtimeit.py -s"import hackysackstackless" hackysackstackless.runit(1000,1000,0)
10 loops, best of 3: 26.9 msec per loop

C:Documents and SettingsgrantDesktopwhy_stacklesscode>c:Python24python.exe c:Python24libtimeit.py -s"import hackysackstackless" hackysackstackless.runit(10000,1000,0)
10 loops, best of 3: 109 msec per loop

C:Documents and SettingsgrantDesktopwhy_stacklesscode>c:Python24python.exe c:Python24libtimeit.py -s"import hackysackstackless" hackysackstackless.runit(100000,1000,0)
10 loops, best of 3: 1.07 sec per loop

甚至直到10,000个线程的时候,那时线程版本早已不能运行了,而这个仍然可以比线程版本在10个线程的时候运行的还快。

这里我在尽量保持代码的简洁,因此你可以相信我的话:计时时间的增长仅仅在于初始化游戏圈子的部分,而真正进行游戏的时间则是一直不变的,不管使用 10个微线程,还是10,000个。这归因于通道的工作方式:当它们收到消息的时候,是立即进行阻塞和恢复操作的。另一方面,各个操作系统线程则是轮番检 查自己的队列里是否有了东西,这意味着,跑着越多的线程,性能就变得越差。

4.4 总结

但愿我已经成功地演示了,微线程的运行至少比操作系统线程快一个数量级,并具备远高于后者的可伸缩性。关于操作系统线程的一般常识是:(1)尽量不要使用它,(2)如果非用不可,就能少用一点就少用一点。而stackless的微线程则使我们从这些限制中解放出来。

5 数据流

5.1 工厂

假设,我们要写程序来模拟一个生产玩具娃娃的工厂,具有如下的需求:

  • 一个仓库,装有用来塑造的塑料球。
  • 一个仓库,装有用来连接部件的铆钉。
  • 一台注塑机,可以在6秒内,用0.2磅塑料球来制造一双手臂。
  • 一台注塑机,可以在5秒内,用0.2磅塑料球来制造一双腿。
  • 一台注塑机,可以在4秒内,用0.1磅塑料球来制造一个头部。
  • 一台注塑机,可以在10秒内,用0.5磅塑料球来制造一个躯干。
  • 一个装配台,可以在2秒内,将一个现成的躯干和一双现成的腿,用一个铆钉装配在一起。
  • 一个装配台,可以在2秒内,将上面的半成品和一双现成的手臂,用一个铆钉装配在一起。
  • 一个装配台,可以在3秒内,将上面的半成品和一个现成的头部,用一个铆钉装配在一起。
  • 每台设备都一直不停地工作下去。

5.2 “普通”版本

如果不用stackless而用“普通”的方法来写这个,将会是很痛苦的事情。当我们经历了这个“普通”版示例之后,会再用stackless来做 一个,并比较两者的代码。如果你认为这个例子太不自然,并且有时间的话,可以稍为休息后,根据上面的需求,自己来做一个工厂的实现,再来将你写出的代码和 stackless版本做个比较。

代码如下:

class storeroom:
    def __init__(self,name,product,unit,count):
        self.product = product
        self.unit = unit
        self.count = count
        self.name = name

    def get(self,count):
        if count > self.count:
            raise RuntimeError("Not enough %s" % self.product)
        else:
            self.count -= count

        return count

    def put(self,count):
        self.count += count

    def run(self):
        pass

rivetStoreroom = storeroom("rivetStoreroom","rivets","#",1000)
plasticStoreroom = storeroom("plastic Storeroom","plastic pellets","lb",100)

class injectionMolder:
    def __init__(self,name,partName,plasticSource,plasticPerPart,timeToMold):
        self.partName = partName
        self.plasticSource = plasticSource
        self.plasticPerPart = plasticPerPart
        self.timeToMold = timeToMold
    self.items = 0
    self.plastic = 0
    self.time = -1
    self.name = name

def get(self,items):
    if items > self.items:
        return 0
    else:
        self.items -= items
        return items

def run(self):
    if self.time == 0:
        self.items += 1
        print "%s finished making part" % self.name
        self.time -= 1
    elif self.time < 0:
        print "%s starts making new part %s" % (self.name,self.partName)
        if self.plastic < self.plasticPerPart:
            print "%s getting more plastic"
            self.plastic += self.plasticSource.get(self.plasticPerPart * 10)
        self.time = self.timeToMold
    else:
        print "%s molding for %s more seconds" % (self.partName, self.time)
        self.time -= 1

armMolder = injectionMolder("arm Molder", "arms",plasticStoreroom,0.2,6)
legMolder = injectionMolder("leg Molder", "leg",plasticStoreroom,0.2,5)
headMolder = injectionMolder("head Molder","head",plasticStoreroom,0.1,4)
torsoMolder = injectionMolder("torso Molder","torso",plasticStoreroom,0.5,10)

class assembler:
    def __init__(self,name,partAsource,partBsource,rivetSource,timeToAssemble):
        self.partAsource = partAsource
        self.partBsource = partBsource
        self.rivetSource = rivetSource
        self.timeToAssemble = timeToAssemble
        self.itemA = 0
        self.itemB = 0
        self.items = 0
        self.rivets = 0
        self.time = -1
        self.name = name

    def get(self,items):
        if items > self.items:
            return 0
        else:
            self.items -= items
            return items

    def run(self):
        if self.time == 0:
            self.items += 1
            print "%s finished assembling part" % self.name
            self.time -= 1
        elif self.time < 0:
            print "%s starts assembling new part" % self.name
            if self.itemA < 1:
                print "%s Getting item A" % self.name
                self.itemA += self.partAsource.get(1)
                if self.itemA < 1:
                    print "%s waiting for item A" % self.name
            elif self.itemB < 1:
                print "%s Getting item B" % self.name
                self.itemB += self.partBsource.get(1)
                if self.itemB < 1:
                    print "%s waiting for item B" % self.name
            print "%s starting to assemble" % self.name
            self.time = self.timeToAssemble
        else:
            print "%s assembling for %s more seconds" % (self.name, self.time)
            self.time -= 1

legAssembler = assembler("leg Assembler",torsoMolder,legMolder,rivetStoreroom,2)
armAssembler = assembler("arm Assembler", armMolder,legAssembler,rivetStoreroom,2)
torsoAssembler = assembler("torso Assembler", headMolder,armAssembler,
                            rivetStoreroom,3)

components = [rivetStoreroom, plasticStoreroom, armMolder,
                legMolder, headMolder, torsoMolder,
              legAssembler, armAssembler, torsoAssembler]

def run():
    while 1:
        for component in components:
            component.run()
        raw_input("Press <ENTER> to continue...")
        print "nnn"

if __name__ == "__main__":
    run()

5.2.1 分析

我们从一个代表仓库的类开始,它的初始化需要一个其所储存的产品的名称、一个衡量单位(如磅,或部件数目)和一个初始存量作为参数。还有一个 run 方法什么也不做,其用途将会在稍后了解。基于这个类,我们建立了两个仓库示例。

接下来是一个注塑机类,它的初始化需要其产品的名称、一个作为塑料来源的仓库、制造一个部件所需要的原料量,和制造一个部件所需的时间作为参数。有 一个 get() 方法,在其内部已有完成的产品时,可将其取出,并调整内部记录。对于这个类, run() 方法是确实做了些事情的:

  • 在计时器大于0期间,塑造过程持续进行,并递减计时器。
  • 当塑造剩余时间达到0,则一个产品被建立,并把计时器设为-1。
  • 当计时器为-1时,注塑机检测是否还有足够的塑料来塑造下一个产品,如果有,则取来原料,并开始塑造。

用这个类,我们建立了四个注塑机实例。

再接下来是一个装配台类,它的初始化需要其产品的名字、部件1的来源、部件2的来源、一个铆钉的仓库,以及装配这些部件所需的时间作为参数。也有一个 get() 方法,在其内部已有完成的产品时,可将其取出,并调整内部记录。而这个类的 run() 方法是这样的:

  • 若计时器大于0,则已经具备原材料的装配台继续其装配过程。
  • 如果计时器等于0,则一个产品被完成,内部记录随之被调整。
  • 如果计时器小于0,则装配台试图取得新的各个部件,并再次开始装配。若其中某个部件还没有来得及塑造出来,则必须等待。

为了装配腿、手臂和头部,各有一个装配台实例被建立。

Note

你会注意到,仓库、注塑机和装配台类有很多相似之处。如果我是在写一个真正生产系统,则很可能先建立一个基类,并使用继承。但在这里,我觉得做出这种类层次关系的话只会使代码变得繁杂,所以有意保持了其简单。

由以上三个类所建立的所有实例,都被装进一个称为 components 的“设备”数组中。然后,我们建立一个事件循环,重复地调用每个设备的 run() 方法。

5.3 走进数据流

如果你熟悉 Unix 系统,那么不管你知不知道数据流技术,恐怕你都已经在使用它了。看下面的 shell 命令:

cat README | more

为了公平,也举出 Windows 中对应的:

type readme.txt | more

尽管,在 Windows 的世界中,数据流技术并不像在 Unix 世界中那么普遍深入。

顺便对还不熟悉 more 工具的读者:这个程序从一个外部来源接收输入,显示一页的内容后暂停,直到用户按下任意键,再显示下一页。这个“|”操作符获取一个程序的输出,并用管道 将其传送到另一个命令的输入。这样,不管 cat 还是 type ,都是将文档内容传送到标准输出,而 more 则接收这些输出。

这样,more 程序仅仅是坐在那里,等着来自另一个程序的数据来流向自己。只要流进的数据足够一定量,就在屏幕上显示一页并暂停;而用户击键时,more 则让后面的数据再流入,并开始再一次等待数据量足够,再显示,再暂停。这便是术语“数据流”。

使用通道,再使用stackless本身的轮转调度器,我们就可以使用数据流技术来写这个工厂的模拟。

5.4 代码的stackless版本

import stackless

#
# “休眠” 辅助函数
#

sleepingTasklets = []
sleepingTicks = 0

def Sleep(secondsToWait):
    channel = stackless.channel()
    endTime = sleepingTicks + secondsToWait
    sleepingTasklets.append((endTime, channel))
    sleepingTasklets.sort()
    # 阻塞,直到收到一个唤醒通知。
    channel.receive()

def ManageSleepingTasklets():
    global sleepingTicks
    while 1:
        if len(sleepingTasklets):
            endTime = sleepingTasklets[0][0]
            while endTime <= sleepingTicks:
                channel = sleepingTasklets[0][1]
                del sleepingTasklets[0]
                # 我们需要发送一些东西,但发什么无所谓,
                # 因为其内容是没用的。
                channel.send(None)
                endTime = sleepingTasklets[0][0] # 检查下一个
        sleepingTicks += 1
        print "1 second passed"
        stackless.schedule()

stackless.tasklet(ManageSleepingTasklets)()

#
# 工厂的实现
#

class storeroom:
    def __init__(self,name,product,unit,count):
        self.product = product
        self.unit = unit
        self.count = count
        self.name = name

    def get(self,count):
        while count > self.count: #重新调度,直到有了足够的原料
            print "%s doesn't have enough %s to deliver yet" % (self.name,
                                                                self.product)
            stackless.schedule()
        self.count -= count
        return count

        return count

    def put(self,count):
        self.count += count

    def run(self):
        pass

rivetStoreroom = storeroom("rivetStoreroom","rivets","#",1000)
plasticStoreroom = storeroom("plastic Storeroom","plastic pellets","lb",100)

class injectionMolder:
    def __init__(self,name,partName,plasticSource,plasticPerPart,timeToMold):
        self.partName = partName
        self.plasticSource = plasticSource
        self.plasticPerPart = plasticPerPart
        self.timeToMold = timeToMold
        self.plastic = 0
        self.items = 0
        self.name = name
        stackless.tasklet(self.run)()

    def get(self,items):
        while items > self.items: #重新调度,直到有了足够的产品
            print "%s doesn't have enough %s to deliver yet" % (self.name,
                                                                self.partName)
            stackless.schedule()
        self.items -= items
        return items

    def run(self):
        while 1:
            print "%s starts making new part %s" % (self.name,self.partName)
            if self.plastic < self.plasticPerPart:
                print "%s getting more plastic"
                self.plastic += self.plasticSource.get(self.plasticPerPart * 10)
            self.plastic -= self.plasticPerPart
            Sleep(self.timeToMold)
            print "%s done molding after %s seconds" % (self.partName,
                                                        self.timeToMold)
            self.items += 1
            print "%s finished making part" % self.name
            stackless.schedule()

armMolder = injectionMolder("arm Molder", "arms",plasticStoreroom,0.2,5)
legMolder = injectionMolder("leg Molder", "leg",plasticStoreroom,0.2,5)
headMolder = injectionMolder("head Molder","head",plasticStoreroom,0.1,5)
torsoMolder = injectionMolder("torso Molder","torso",plasticStoreroom,0.5,10)

class assembler:
    def __init__(self,name,partAsource,partBsource,rivetSource,timeToAssemble):
        self.partAsource = partAsource
        self.partBsource = partBsource
        self.rivetSource = rivetSource
        self.timeToAssemble = timeToAssemble
        self.itemA = 0
        self.itemB = 0
        self.items = 0
        self.rivets = 0
        self.name = name
        stackless.tasklet(self.run)()

    def get(self,items):
        while items > self.items: #重新调度,直到有了足够的产品
            print "Don't have a %s to deliver yet" % (self.name)
            stackless.schedule()
        self.items -= items
        return items

    def run(self):
        while 1:
            print "%s starts assembling new part" % self.name
            self.itemA += self.partAsource.get(1)
            self.itemB += self.partBsource.get(1)
            print "%s starting to assemble" % self.name
            Sleep(self.timeToAssemble)
            print "%s done assembling after %s" % (self.name, self.timeToAssemble)
            self.items += 1
            print "%s finished assembling part" % self.name
            stackless.schedule()

legAssembler = assembler("leg Assembler",torsoMolder,legMolder,rivetStoreroom,2)
armAssembler = assembler("arm Assembler", armMolder,legAssembler,rivetStoreroom,2)
torsoAssembler = assembler("torso Assembler", headMolder,armAssembler,
                            rivetStoreroom,3)

def pause():
    while 1:
        raw_input("Press <ENTER> to continue...")
        print "nnn"
        stackless.schedule()

stackless.tasklet(pause)()

def run():
    stackless.run()

if __name__ == "__main__":
    run()

5.4.1 分析

5.4.1.1 休眠功能

首先我们建立了一些辅助函数,好让我们的类可以进行“休眠”。一个微进程调用 Sleep() ,则先建立一个通道,再计算出将被唤醒的时间,并将这个时间信息添加到全局数组 sleepingTasklets 中。之后,将调用 channel.receive() ,这将使该微进程暂停运行,直到被再次唤醒。

接着我们建立另一个函数,来管理所有休眠的微进程。它检查全局数组 sleepingTasklets ,找出所有需要立即被唤醒的成员,并通过其通道来将其唤醒。这个函数也被添加到了微进程调度器中。

5.4.1.2 类

这些类与“普通”版本中的类相似,但也有一些显著不同:首先,在实例化的时候,他们的 run() 方法创建了微进程,这样我们不再需要手工建立一个设备数组,和一个外部的 run() 函数来处理事件循环,stackless本身就隐式地做了这些工作。其次的不同是,微进程可以通过休眠来等待一个产品被产出,而不用通过计数器来计时。第 三个不同,则是对 get() 的调用变得更自然了,如果某种原材料没有准备好,则这个微进程简单地重新进入调度循环,直到有了原材料。

5.5 那我们获得了什么?

OK,两个版本的程序都能运行,并得到同样的结果,那这里究竟有什么大不了的事情?——让我们查看一下普通版本的工厂的 run 方法:

def run(self):
    if self.time == 0:
        self.items += 1
        print "%s finished assembling part" % self.name
        self.time -= 1
    elif self.time < 0:
        print "%s starts assembling new part" % self.name
        if self.itemA < 1:
            print "%s Getting item A" % self.name
            self.itemA += self.partAsource.get(1)
            if self.itemA < 1:
                print "%s waiting for item A" % self.name
        elif self.itemB < 1:
            print "%s Getting item B" % self.name
            self.itemB += self.partBsource.get(1)
            if self.itemB < 1:
                print "%s waiting for item B" % self.name
        print "%s starting to assemble" % self.name
        self.time = self.timeToAssemble
    else:
        print "%s assembling for %s more seconds" % (self.name, self.time)
        self.time -= 1

再看 stackless 的版本:

def run(self):
    while 1:
        print "%s starts assembling new part" % self.name
        self.itemA += self.partAsource.get(1)
        self.itemB += self.partBsource.get(1)
        print "%s starting to assemble" % self.name
        Sleep(self.timeToAssemble)
        print "%s done assembling after %s" % (self.name, self.timeToAssemble)
        self.items += 1
        print "%s finished assembling part" % self.name
        stackless.schedule()

Stackless 的版本比普通的版本更加简单、清晰和直观,它不需要将事件循环的基础结构包装进 run 方法中,这个结构已经和 run() 方法解除了耦合。run() 方法仅仅描述了自己要做什么,而不需要关心具体究竟怎么做的。这就使软件开发者能集中精力于工厂的运作,而不是事件循环以及程序本身的运作。

5.6 推(push)数据

Note

本节的完整程序保存为 digitalCircuit.py ,在本文的末尾,和代码.zip文件中和都有。

在工厂的例子中,我们是在“拉”数据:每个部分都去请求其所需要的部件,并一直等待那些部件到来。我们也可以来“推”数据,这样,系统中的每个部分都将自身的变化向下传播到另一个部分。“拉”的方式,称为“ 懒惰数据流 ”,而“推”的方式则称为“ 急切数据流 ”。

为了演示“推”的方式,我们来建立一个数字电路的模拟器。这个模拟器由各种元件组成,元件具有0或1的状态,并可以各种方式互相连接起来。这里我们使用面向对象的方法,并定义一个 EventHandler 基类来实现其大部分功能:

class EventHandler:
    def __init__(self,*outputs):
        if outputs==None:
            self.outputs=[]
        else:
            self.outputs=list(outputs)

        self.channel = stackless.channel()
        stackless.tasklet(self.listen)()

    def listen(self):
        while 1:
            val = self.channel.receive()
            self.processMessage(val)
            for output in self.outputs:
                self.notify(output)

    def processMessage(self,val):
        pass

    def notify(self,output):
        pass

    def registerOutput(self,output):
        self.outputs.append(output)

    def __call__(self,val):
    self.channel.send(val)

EventHandler 类的核心功能,是做以下三件事:

  • 通过 listen 方法,持续地监听一个通道上传来的消息。
  • 之后,通过 processMessage 方法,处理所有收到的消息。
  • 最后,通过 notify 方法,将处理结果通知到所有注册的输出端。

还有两个附加的辅助方法:

  • registerOutput 用来在实例建立之后,再注册额外的输出端。
  • __call__ 被重载,作为一种便利,使我们可以以这种格式来发送消息:
    event(1)

从而无需这样:

event.channel.send(1)

使用 EventHandler 类作为基本构建单元,我们可以开始实现这个数字电路模拟器,由一个开关开始。下面描述的是一个可由用户控制的开关,可以向其发送0或1的值:

class Switch(EventHandler):
    def __init__(self,initialState=0,*outputs):
        EventHandler.__init__(self,*outputs)
        self.state = initialState

    def processMessage(self,val):
        debugPrint("Setting input to %s" % val)
        self.state = val

    def notify(self,output):
        output((self,self.state))

初始化之后,这个开关就保存着其初始的状态,而 processMessage 则被重载,用来将收到的消息保存起来,成为新的当前状态。其 notify 方法则被重载为发送一个元组,其中含有指向实例自身的引用,还有其状态。我们不久后会看到,我们需要顺便发送这个自身的引用,这样,那些具有多个输入端的 元件则可以判别,消息来自于哪个来源。

Note

若你正在随着我们的进度来调试代码,则别忘了我们还在使用 debugPrint() 函数来提供诊断信息,它最初是在“轻量级线程”这一节中定义的。

接下来我们要建立的,是“指示器”类,这个类的实例的作用仅仅是将其当前状态输出。我想我们可以认为,其相当于真正的数字电路中的发光二极管:

class Reporter(EventHandler):
    def __init__(self,msg="%(sender)s send message %(value)s"):
        EventHandler.__init__(self)
        self.msg = msg

    def processMessage(self,msg):
        sender,value=msg
        print self.msg % {'sender':sender,'value':value}

其初始化接受一个可选的格式字符串,来指定之后输出的样式。代码的其他部分意义自明。

现在我们有了一个足够好的框架,来测试这些最初的功能:

C:Documents and SettingsgrantDesktopwhy_stacklesscode>c:Python24python.ex
e
Python 2.4.3 Stackless 3.1b3 060516 (#69, May  3 2006, 11:46:11) [MSC v.1310 32
bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import stackless
>>> from digitalCircuit import *
>>>
>>> reporter = Reporter()
>>> switch = Switch(0,reporter) #创建一个开关,并连接到一个指示器做输出。
>>>
>>> switch(1)
<digitalCircuit.Switch instance at 0x00A46828> send message 1
>>>
>>> switch(0)
<digitalCircuit.Switch instance at 0x00A46828> send message 0
>>>
>>> switch(1)
<digitalCircuit.Switch instance at 0x00A46828> send message 1
>>>

与先前设计的工厂不同,对开关的操作会使结果立即被推至其输出端,并显示出来。

现在我们来建立一些数字逻辑部件,首先是反相器,它接受一个输入,并将其逻辑相反的值推出,就是说输入0会输出1,输入1会输出0:

class Inverter(EventHandler):
    def __init__(self,input,*outputs):
        EventHandler.__init__(self,*outputs)
        self.input = input
        input.registerOutput(self)
        self.state = 0

    def processMessage(self,msg):
        sender,value = msg
        debugPrint("Inverter received %s from %s" % (value,msg))
        if value:
            self.state = 0
        else:
            self.state = 1

反相器的初始化参数为一个输入端,即另外某个 EventHandler ,将它保存下来,并将自身注册为它的一个输出端。而 processMessage() 方法,则将自身的状态设为收到的消息的逻辑相反值。与 Switch 类类似,反相器类的 notify 事件也发送一个由其自身和其状态所组成的元组。

我们可以修改上面的例子,在开关和指示器之间串联入一个反相器。如有兴趣,尽可一试,但这个过程我认为已经没有必要列出了。

接下来是一个与门,这是我们遇到的第一个有多个输入端的类。它有两个输入端,如果都被置为1,则送出消息1,否则送出消息0:

class AndGate(EventHandler):
    def __init__(self,inputA,inputB,*outputs):
        EventHandler.__init__(self,*outputs)

        self.inputA = inputA
        self.inputAstate = inputA.state
        inputA.registerOutput(self)

        self.inputB = inputB
        self.inputBstate = inputB.state
        inputB.registerOutput(self)

        self.state = 0

    def processMessage(self,msg):
        sender, value = msg
        debugPrint("AndGate received %s from %s" % (value,sender))

        if sender is self.inputA:
            self.inputAstate = value
        elif sender is self.inputB:
            self.inputBstate = value
        else:
            raise RuntimeError("Didn't expect message from %s" % sender)

        if self.inputAstate and self.inputBstate:
            self.state = 1
        else:
            self.state = 0
        debugPrint("AndGate's new state => %s" % self.state)

    def notify(self,output):
        output((self,self.state))

在与门的 processMessage 方法中,我们需要判定,是哪个输入端送来了消息,并据此设置状态。这就是为什么别的部件送来的消息中需要含有其自身的引用。

最后我们做出或门。它和与门类似,只是,它只要有任一个输入端为1的时候就送出消息1,只有两个输入端都为0的时候才送出0:

class OrGate(EventHandler):
    def __init__(self,inputA,inputB,*outputs):
        EventHandler.__init__(self,*outputs)

        self.inputA = inputA
        self.inputAstate = inputA.state
        inputA.registerOutput(self)

        self.inputB = inputB
        self.inputBstate = inputB.state
        inputB.registerOutput(self)

        self.state = 0

    def processMessage(self,msg):
        sender, value = msg
        debugPrint("OrGate received %s from %s" % (value,sender))

        if sender is self.inputA:
            self.inputAstate = value
        elif sender is self.inputB:
            self.inputBstate = value
        else:
            raise RuntimeError("Didn't expect message from %s" % sender)

        if self.inputAstate or self.inputBstate:
            self.state = 1
        else:
            self.state = 0
        debugPrint("OrGate's new state => %s" % self.state)

    def notify(self,output):
        output((self,self.state))

5.6.1 半加器

作为结束,我们将使用我们已经建立的所有部件,来构建一个半加器。半加器实现两个比特的加法。我们将一些部件连接了起来,然后来“拨动”开关。开关的动作改变了其状态,并且把其变化,以数据流的方式,通过系统传播了下去:

if __name__ == "__main__":
    # 半加器
    inputA = Switch()
    inputB = Switch()
    result = Reporter("Result = %(value)s")
    carry = Reporter("Carry = %(value)s")
    andGateA = AndGate(inputA,inputB,carry)
    orGate = OrGate(inputA,inputB)
    inverter = Inverter(andGateA)
    andGateB = AndGate(orGate,inverter,result)
    inputA(1)
    inputB(1)
    inputB(0)
    inputA(0)

6 角色

在角色的模型里面,一切都是角色(废话!)。角色就是一个对象(一般意义上的对象,而不必是面向对象中的意义),它可以:

  • 从其他角色接收消息。
  • 对收到的消息中适合于自己的,进行处理。
  • 向其它角色发送消息。
  • 创建新的角色。

一个角色对其它的角色并不具有直接的访问渠道,所有的交流都通过消息传递来完成。这就提供了丰富的模型,来模拟现实世界中的对象——它们是彼此松散耦合的,并对彼此的内部所知有限。

如果我们要建立一个模拟过程的话,就来模拟一下……

6.1 杀手机器人

Note

本节的完整程序保存为actors.py,在本文的末尾,和代码zip文件中都有。

6.1.1 角色基类

在这个例子中,我们将配置出一个小小的世界,在其中有一些使用角色模型来移动和战斗的机器人。作为开始,我们来定义所有角色的基类:

class actor:
    def __init__(self):
        self.channel = stackless.channel()
        self.processMessageMethod = self.defaultMessageAction
        stackless.tasklet(self.processMessage)()

    def processMessage(self):
        while 1:
            self.processMessageMethod(self.channel.receive())

    def defaultMessageAction(self,args):
        print args

默认情况下,角色建立一个通道来接收消息,指定一个方法来处理这些消息,并启动一个循环来将接收的消息分派给处理方法。默认的处理过程只是把收到的消息显示出来。这些,已经是我们实现角色模型所需要的全部。

6.1.2 消息的格式

所有发送的消息都遵从一个格式:先是发送者的通道,接着一个字符串为消息的名称,再接下来是可选的参数。例如:

(self.channel, "JOIN", (1,1) )
(self.channel, "COLLISION")
等等……

注意,我们只将发送者的通道随消息送出,而不是整个发送者对象。在角色模型中,角色间的所有交流都必须通过消息传递来体现,如果将 self 都发送出去的话,则使得对方可以很容易地用非正常手段对发送者的内部未知信息进行访问。

事实上你会注意到,当我们将本节的大部分角色实例化的时候,甚至不需要将其赋值给能被别的角色访问到的变量。我们仅仅创建它们,并让它们独自漂浮在那里,对周围环境只有有限的了解。

6.1.3 世界(world)类

“世界”角色,扮演着其他所有角色相互作用的中央枢纽。其他角色发送 JOIN 消息给世界角色,后者则跟踪它们。周期性地,世界角色发出 WORLD_STATE 消息,其中包括关于所有可见的角色的信息,来供它们内部处理:

class world(actor):
    def __init__(self):
        actor.__init__(self)
        self.registeredActors = {}
        stackless.tasklet(self.sendStateToActors)()

    def testForCollision(self,x,y):
        if x < 0 or x > 496:
            return 1
        elif y < 0 or y > 496:
            return 1
        else:
            return 0

    def sendStateToActors(self):
        while 1:
            for actor in self.registeredActors.keys():
                actorInfo = self.registeredActors[actor]
                if self.registeredActors[actor][1] != (-1,-1):
                    VectorX,VectorY = (math.sin(math.radians(actorInfo[2])) * actorInfo[3],
                                       math.cos(math.radians(actorInfo[2])) * actorInfo[3])
                    x,y = actorInfo[1]
                    x += VectorX
                    y -= VectorY
                    if self.testForCollision(x,y):
                        actor.send((self.channel,"COLLISION"))
                    else:
                        self.registeredActors[actor] = tuple([actorInfo[0],
                                                          (x,y),
                                                              actorInfo[2],
                                                              actorInfo[3]])
            worldState = [self.channel, "WORLD_STATE"]
            for actor in self.registeredActors.keys():
                if self.registeredActors[actor][1] != (-1,-1):
                    worldState.append( (actor, self.registeredActors[actor]))
            message = tuple(worldState)
            for actor in self.registeredActors.keys():
                actor.send(message)
            stackless.schedule()

    def defaultMessageAction(self,args):
        sentFrom, msg, msgArgs = args[0],args[1],args[2:]
        if msg == "JOIN":
            print 'ADDING ' , msgArgs
            self.registeredActors[sentFrom] = msgArgs
        elif msg == "UPDATE_VECTOR":
            self.registeredActors[sentFrom] = tuple([self.registeredActors[sentFrom][0],
                                                     self.registeredActors[sentFrom][1],
                                                     msgArgs[0],msgArgs[1]])
        else:
            print '!!!! WORLD GOT UNKNOWN MESSAGE ' , args

World = world().channel

除了处理消息的微进程外,“世界”角色还建立了另一个独立的微进程,来执行 sendStateToActors() 方法。这个方法里有个循环,用于构建关于世界状态的信息,并发送给所有的角色。这是其它角色唯一可以指望接收到的消息。若有必要,它们可以回应这个消息, 即将某种 UPDATE 消息发回给世界。

作为 sendStateToActors() 方法的一部分,“世界”角色需要更新其内部的、对可移动的角色的位置的记录。它使用可移动角色的角度和速度来建立一个矢量,确保更新后的位置不会撞到世界的四面边界,并存下其新的位置。

defaultMessageAction() 方法处理以下已知信息,并忽略其他的:

JOIN
将一个角色添加到世界中的已知角色列表,其参数包括新角色的位置、角度和速度。位置-1, -1表示这个角色对其它角色不可见,比如后面将要详述的显示屏角色。
UPDATE_VECTOR
为发送这个消息的角色设置新的角度和速度。

最后,一个“世界”角色被实例化,其通道被保存进全局变量 World 中,使其它角色可以发送它们最初的 JOIN 消息。

6.1.4 一个简单机器人

我们将以一个简单的机器人开始,它以恒定的速度移动,每接到一个 WORLD_STATE 消息的时候,都顺时针旋转1度作为响应。当发生与世界边界碰撞的 COLLISION 事件时,它将旋转73度再尝试前进。所有其他消息将被忽略。

class basicRobot(actor):
    def __init__(self,location=(0,0),angle=135,velocity=1,world=World):
        actor.__init__(self)
        self.location = location
        self.angle = angle
        self.velocity = velocity
        self.world = world

        joinMsg =(self.channel,"JOIN",self.__class__.__name__,
                  self.location,self.angle,self.velocity)
        self.world.send(joinMsg)

    def defaultMessageAction(self,args):
        sentFrom, msg, msgArgs = args[0],args[1],args[2:]
        if msg == "WORLD_STATE":
            self.location = (self.location[0] + 1, self.location[1] + 1)
            self.angle += 1
            if self.angle >= 360:
                self.angle -= 360

            updateMsg = (self.channel, "UPDATE_VECTOR",
                         self.angle,self.velocity)
            self.world.send(updateMsg)
        elif msg == "COLLISION":
            self.angle += 73
            if self.angle >= 360:
                self.angle -= 360
        else:
            print "UNKNOWN MESSAGE", args

basicRobot(angle=135,velocity=5)
basicRobot((464,0),angle=225,velocity=10)

stackless.run()

注意,机器人的构造方法发出了一个 JOIN 消息到“世界”对象,来注册自己。除此之外,代码应该还算易懂的。

6.1.5 蹊跷(Detour)的PyGame

至此,在示例程序中,我们都是使用调试输出语句来显示事情进行的过程。我试图以这种方式来保持代码的简单易懂,但有些时候,输出语句却变得不再直观,而是越发迷惑。在“数据流”一节中我们已经用得很勉强了,而在本节中,情况已经变得复杂到无法再尝试用打印输出来表示了。

Note

要运行本节的代码,需要安装 pyGame 的当前版本,可以从这里取得:http://www.pygame.org/

我决定使用 pyGame 来创建一个简单的可视化引擎。尽管对于 pyGame 内容的叙述已经超出了本教程的范围,但其操作本身还是相对简明的。当显示屏角色收到一个 WORLD_STATE 消息,就将相应的角色放置上去,并更新显示。很幸运,我们可以将所有的 pyGame 代码隔离在一个角色之内,因此代码的其它部分可以保持不被“污染”,依然可以被理解,哪怕不了解也不关心 pyGame 怎么进行的显示渲染:

class display(actor):
    def __init__(self,world=World):
        actor.__init__(self)

        self.world = World

        self.icons = {}
        pygame.init()

        window = pygame.display.set_mode((496,496))
        pygame.display.set_caption("Actor Demo")

        joinMsg = (self.channel,"JOIN",self.__class__.__name__, (-1,-1))
        self.world.send(joinMsg)

    def defaultMessageAction(self,args):
        sentFrom, msg, msgArgs = args[0],args[1],args[2:]
        if msg == "WORLD_STATE":
            self.updateDisplay(msgArgs)
        else:
            print "UNKNOWN MESSAGE", args

    def getIcon(self, iconName):
        if self.icons.has_key(iconName):
            return self.icons[iconName]
        else:
            iconFile = os.path.join("data","%s.bmp" % iconName)
            surface = pygame.image.load(iconFile)
            surface.set_colorkey((0xf3,0x0a,0x0a))
            self.icons[iconName] = surface
            return surface

    def updateDisplay(self,actors):

        for event in pygame.event.get():
            if event.type == pygame.QUIT: sys.exit()

        screen = pygame.display.get_surface()

        background = pygame.Surface(screen.get_size())
        background = background.convert()
        background.fill((200, 200, 200))

        screen.blit(background, (0,0))
        for item in actors:
            screen.blit(pygame.transform.rotate(self.getIcon(item[1][0]),-item[1][2]), item[1][1])
        pygame.display.flip()

display()

这里获取了 WORLD_STATE ,并基于此创建了显示屏。

Note

为了使本节的示例得以运行,需要在Python下安装 pyGame 。你也可能需要下载我所建立的、可选的图标,并将其解压到你的代码目录中。

6.1.6 第一轮代码

现在我们做了足够的准备,来运行程序的第一个版本。运行后,两个简单机器人将会开始迂回移动,并在边界反弹。

6.2 又一蹊跷:机理的模拟

Note

本节的完整程序保存为 actors2.py ,在本文的末尾,和代码.zip文件中和都有。

作为另一条蹊径,我们需要实现某种游戏(呃……我是指,模拟)的机理。严格来说,这些机理与角色模型一点关系都没有。然而,为了建立一种丰富而逼真 的模拟,我们不能被它们所阻碍。本节将详述我们将要达到的目标,以及如何达到。在这之后,我们用来摆弄角色的这个环境将变得更加有用。

6.2.1 角色属性

随着“世界”角色需要跟踪的信息变得越发复杂,在初始的 JOIN 消息中带送一串参数的方式也变得越来越麻烦。为了使这更容易,我们建立一个属性集对象,来跟踪这些信息,这个将取代分立的参数,在 JOIN 消息中被传递。

class properties:
    def __init__(self,name,location=(-1,-1),angle=0,
                 velocity=0,height=-1,width=-1,hitpoints=1,physical=True,
                 public=True):
        self.name = name
        self.location = location
        self.angle = angle
        self.velocity = velocity
        self.height = height
        self.width = width
        self.public = public
        self.hitpoints = hitpoints

注意,属性集对象是为了在角色之间传递信息而建立的,我们不会在建立它的那个角色局部再存储一份,否则,“世界”角色就能够直接修改其它角色的内部内容了,而不是通过发送消息来修改。

6.2.2 碰撞检测

在上一个程序中,碰撞检测的过程是有点问题的,其中最明显的就是角色不会彼此相撞,两个弹来跳去的机器人只会彼此穿过,而不是发生碰撞。第二个问题 则是我们没有指明角色的尺寸,这在机器人撞到右边界或下边界的时候表现最为明显:在 COLLISION 消息起作用之前,它们看上去有部分越过了边界。我确信,碰撞检测这主题是可以写几本书的,但这里我们将试图坚持使用一个相当简单的版本,对我们的目的来说 这已经够好了。

首先,我们给每个角色加上高度与宽度属性,这就能够给角色建立一个“边框”。原来的“位置”属性已经确定了这个边框的左上角,而新加的高度和宽度属性则同时确定了其右下角,这就对角色的物理形状给出了一个合理的近似表示。

为了检测与世界边缘的碰撞,现在,我们检测角色边框的四角中任一个是否与边缘有碰撞;为了检测和其它物体的碰撞,我们将维护一个已被检测过碰撞的物体的列表。我们遍历这个列表,找出是否有哪个角色的四角之一处在了另一个角色的边框之内,若存在,则它们是碰撞的。

这些就是我们的基本碰撞检测系统的全部,下面是检测单个碰撞的函数:

def testForCollision(self,x,y,item,otherItems=[]):
    if x < 0 or x + item.width > 496:
        return self.channel
    elif y < 0 or y+ item.height > 496:
        return self.channel
    else:
        ax1,ax2,ay1,ay2 = x, x+item.width, y,y+item.height
        for item,bx1,bx2,by1,by2 in otherItems:
            if self.registeredActors[item].physical == False: continue
            for x,y in [(ax1,ay1),(ax1,ay2),(ax2,ay1),(ax2,ay2)]:
                if x >= bx1 and x <= bx2 and y >= by1 and y <= by2:
                    return item
            for x,y in [(bx1,by1),(bx1,by2),(bx2,by1),(bx2,by2)]:
                if x >= ax1 and x <= ax2 and y >= ay1 and y <= ay2:
                    return item
        return None

还有个方法,用来遍历所有角色并检测,它在 sendStateToActors() 微进程中被调用:

def updateActorPositions(self):
    actorPositions = []
    for actor in self.registeredActors.keys():
        actorInfo = self.registeredActors[actor]
        if actorInfo.public and actorInfo.physical:
            x,y = actorInfo.location
            angle = actorInfo.angle
            velocity = actorInfo.velocity
            VectorX,VectorY = (math.sin(math.radians(angle)) * velocity,
                               math.cos(math.radians(angle)) * velocity)
            x += VectorX/self.updateRate
            y -= VectorY/self.updateRate
            collision = self.testForCollision(x,y,actorInfo,actorPositions)
            if collision:
                #不移动
                actor.send((self.channel,"COLLISION",actor,collision))
                if collision and collision is not self.channel:
                    collision.send((self.channel,"COLLISION",actor,collision))
            else:
                actorInfo.location = (x,y)
            actorPositions.append( (actor,
                                    actorInfo.location[0],
                                    actorInfo.location[0] + actorInfo.height,
                                    actorInfo.location[1],
                                    actorInfo.location[1] + actorInfo.width))

6.2.3 恒定的时间

我们的模拟有另一个问题,就是它在不同的机器上运行,会消耗不同的时间。若你的机器比我的快,你可能基本看不清机器人了,或者比我的慢,则机器人好像在爬一样。

为了修正这个,我们将以恒定的速度来产生 WORLD_STATE 消息,默认情况下每1/30秒产生一次。如果这点可以标准化,事情就容易了。然而,在机器无法应付负载量的情况下,我们也要有能力进行修正,来维持这个刷 新率。如果产生一个帧的时间超过了1/30秒(不管是由于程序本身的复杂性,还是外部某个程序狂占资源),我们就需要调整刷新率。

在我们的例子中,如果完成一帧的计算的时间超过了刷新率所确定的周期时间,就将每秒刷新次数降低一次;在在当前刷新率下,若显示一帧的过程中的空闲时间占到了40%或更多,则将每秒刷新次数增加一次,最大不超过每秒30次刷新。

这样,我们就能够在不同的机器上以同样的速度运行了,但这引入了另一个有趣的问题:例如现在,在每次刷新的时候,我们都将机器人的角度调整1度,并 重设其速度。这样的话,我们让这程序在不同的机器上各运行10秒钟,其中一台机器跑到每秒20次刷新,另一台则跑到每秒30次,那么最后,两台机器上的机 器人的位置将不会相同,这显然是不应该的。我们需要将角色数据的更新也改成基于时间的。

在本例中,我们不再使用每帧调整1度角度并调整一次速度(例如5点)的做法,而是将这个调整变成基于流逝时间的,比如,可以在每个时间单位里调整角度30.0度,调整速度150.0点。这样就可以在任何刷新率下都得到一致的行为。

为了实现这点,我们将修改 WORLD_STATE 消息,使之既包含当前时间又包含刷新率。于是,收到这种消息的角色就可以计算出合适的更新结果。

实现刷新率的代码如下:

def runFrame(self):
    initialStartTime = time.clock()
    startTime = time.clock()
    while 1:
        self.killDeadActors()
        self.updateActorPositions()
        self.sendStateToActors(startTime)
        #等待
        calculatedEndTime = startTime + 1.0/self.updateRate

        doneProcessingTime = time.clock()
        percentUtilized =  (doneProcessingTime - startTime) / (1.0/self.updateRate)
        if percentUtilized >= 1:
            self.updateRate -= 1
            print "TOO MUCH LOWERING FRAME RATE: " , self.updateRate
        elif percentUtilized <= 0.6 and self.updateRate < self.maxupdateRate:
            self.updateRate += 1
            print "TOO MUCH FREETIME, RAISING FRAME RATE: " , self.updateRate

        while time.clock() < calculatedEndTime:
            stackless.schedule()
        startTime = calculatedEndTime

        stackless.schedule()

6.2.4 伤害值、生命值和死亡

现在,我们的机器人是无敌的,将会永远运行下去,这可不怎么好玩——它们应该只能承受有限的伤害,然后死亡才对。为了实现这个,我们得增加一些新型 的消息:DAMAGE 消息包含一个参数,指定其受到的伤害值,这个值将从机器人类的另一个新属性“生命值”(hitpoints)之中减去。如果生命值达到小于或等于0,则这 个角色将向“世界”角色发送一条 KILLME 消息。以下是从机器人的 defaultMessageAction() 方法中摘录的的代码,用来实现这一过程:

elif msg == "DAMAGE":
    self.hitpoints -= msgArgs[0]
    if self.hitpoints <= 0:
        self.world.send( (self.channel,"KILLME") )
else:
    print "UNKNOWN MESSAGE", args

另外,我们随手规定, COLLISION 消息也会使生命值降低一点,如果满足条件,也会导致发送 KILLME 消息。

当世界角色收到 KILLME 消息,便将其内部对这个角色的生命值的记录修改为0,接着,将删除那些生命值小于或等于0的角色,作为常规更新的一部分:

def killDeadActors(self):
    for actor in self.registeredActors.keys():
        if self.registeredActors[actor].hitpoints <= 0:
            print "ACTOR DIED", self.registeredActors[actor].hitpoints
            actor.send_exception(TaskletExit)
            del self.registeredActors[actor]

注意,在这里我们引入了通道的 send_exception() 方法。与普通的 send 不同,这将使接收的微进程所调用的 channel.receive() 方法抛出一个异常。此处我们抛出的是 stackless 中的 TaskletExit 异常,这将使一个微进程安静地结束。你也可以抛出其他任何异常,但如果不加以捕获的话,这个异常就会在主微进程中再被抛出一次。

6.2.5 第二轮代码

这个程序的完整版本,仍然算不上特别令人激动,但如果你运行它,你将看到,上面我们所添加的所有特性都起了作用:机器人在经过了足够数量的碰撞之后,最终会死亡,从屏幕上消失。

6.3 回到角色:一起抓狂

Note

本节的完整程序保存为 actors3.py ,在本文的末尾,和代码.zip文件中和都有。

现在对于机理的模拟已经不成问题,我们就可以开始用这个程序做一些有趣的事情了,首先……

6.3.1 爆炸

机器人死亡的时候,让它们简单地消失可没什么意思,至少应该让它们爆炸。机器人在死亡的时候将会创建一个“爆炸”角色,这个角色并不是物质化的,而是仅仅显示一个爆炸的图像。它会在3秒钟后将自己删除,爆炸图像也随之消失:

class explosion(actor):
    def __init__(self,location=(0,0),angle=0,world=World):
        actor.__init__(self)
        self.time = 0.0
        self.world = world
        self.world.send((self.channel,"JOIN",
                            properties(self.__class__.__name__,
                                       location = location,
                                       angle = angle,
                                       velocity=0,
                                       height=32.0,width=32.0,hitpoints=1,
                                       physical=False)))

    def defaultMessageAction(self,args):
        sentFrom, msg, msgArgs = args[0],args[1],args[2:]
        if msg == "WORLD_STATE":
            WorldState = msgArgs[0]
            if self.time == 0.0:
                self.time = WorldState.time
            elif WorldState.time >= self.time + 3.0:
                self.world.send( (self.channel, "KILLME") )

6.3.2 埋雷机器人

现在我们来创建能埋地雷的机器人。在创建这种机器人的类之前,先得创建一个“地雷”类:

class mine(actor):
    def __init__(self,location=(0,0),world=World):
        actor.__init__(self)
        self.world = world
        self.world.send((self.channel,"JOIN",
                            properties(self.__class__.__name__,
                                       location=location,
                                       angle=0,
                                       velocity=0,
                                       height=2.0,width=2.0,hitpoints=1)))

    def defaultMessageAction(self,args):
        sentFrom, msg, msgArgs = args[0],args[1],args[2:]
        if msg == "WORLD_STATE":
            pass
        elif msg == "COLLISION":
            if msgArgs[0] is self.channel:
                other = msgArgs[1]
            else:
                other = msgArgs[0]
            other.send( (self.channel,"DAMAGE",25) )
            self.world.send( (self.channel,"KILLME"))
            print "MINE COLLISION"
        else:
            print "UNKNOWN MESSAGE", args

这是个简单的角色,只是单纯地停在那里,直到有东西撞上它,就向这个撞它的东西送出25点伤害,并将自己删除。

埋雷机器人与普通机器人相似,除了几点不同:首先,为了使局面混杂,我将埋雷机器人设定为为曲折移动,而不是缓慢地转向一个方向。其次,它每一秒都创建一个地雷,立即放置在自己身后:

class minedropperRobot(actor):
    def __init__(self,location=(0,0),angle=135,velocity=1,
                 hitpoints=20,world=World):
        actor.__init__(self)
        self.location = location
        self.angle = angle
        self.delta = 0.0
        self.height=32.0
        self.width=32.0
        self.deltaDirection = "up"
        self.nextMine = 0.0
        self.velocity = velocity
        self.hitpoints = hitpoints
        self.world = world
        self.world.send((self.channel,"JOIN",
                            properties(self.__class__.__name__,
                                       location=self.location,
                                       angle=self.angle,
                                       velocity=self.velocity,
                                       height=self.height,width=self.width,
                                       hitpoints=self.hitpoints)))

    def defaultMessageAction(self,args):
        sentFrom, msg, msgArgs = args[0],args[1],args[2:]
        if msg == "WORLD_STATE":
            for actor in msgArgs[0].actors:
                if actor[0] is self.channel:
                    break
            self.location = actor[1].location
            if self.deltaDirection == "up":
                self.delta += 60.0 * (1.0 / msgArgs[0].updateRate)
                if self.delta > 15.0:
                    self.delta = 15.0
                    self.deltaDirection = "down"
            else:
                self.delta -= 60.0 * (1.0 / msgArgs[0].updateRate)
                if self.delta < -15.0:
                    self.delta = -15.0
                    self.deltaDirection = "up"
            if self.nextMine <= msgArgs[0].time:
                self.nextMine = msgArgs[0].time + 1.0
                mineX,mineY = (self.location[0] + (self.width / 2.0) ,
                               self.location[1] + (self.width / 2.0))

                mineDistance = (self.width / 2.0 ) ** 2
                mineDistance += (self.height / 2.0) ** 2
                mineDistance = math.sqrt(mineDistance)

                VectorX,VectorY = (math.sin(math.radians(self.angle + self.delta)),
                                   math.cos(math.radians(self.angle + self.delta)))
                VectorX,VectorY = VectorX * mineDistance,VectorY * mineDistance
                x,y = self.location
                x += self.width / 2.0
                y += self.height / 2.0
                x -= VectorX
                y += VectorY
                mine( (x,y))

            updateMsg = (self.channel, "UPDATE_VECTOR",
                         self.angle + self.delta ,self.velocity)
            self.world.send(updateMsg)
        elif msg == "COLLISION":
            self.angle += 73.0
            if self.angle >= 360:
                self.angle -= 360
            self.hitpoints -= 1
            if self.hitpoints <= 0:
                explosion(self.location,self.angle)
                self.world.send((self.channel,"KILLME"))
        elif msg == "DAMAGE":
            self.hitpoints -= msgArgs[0]
            if self.hitpoints <= 0:
                explosion(self.location,self.angle)
                self.world.send((self.channel, "KILLME"))
        else:
            print "UNKNOWN MESSAGE", args

6.3.3 建造台

建造台每5秒钟就在其位置上简单地创建一个新的、具有随机的属性的机器人。在其构造函数中有一些猫腻:它并不是特意创建一个含有所有合法的机器人的 数组,而是使用自省,来查找所有的名称以“Robot”结尾的类,并将它们加入到一个列表。这样,如果你创建一个自己的机器人类,你将不需要在这个建造台 类上特意进行某种注册工作。这个类除此之外的部分还是相当易懂的:

class spawner(actor):
    def __init__(self,location=(0,0),world=World):
        actor.__init__(self)
        self.location = location
        self.time = 0.0
        self.world = world

        self.robots = []
        for name,klass in globals().iteritems():
            if name.endswith("Robot"):
                self.robots.append(klass)

        self.world.send((self.channel,"JOIN",
                            properties(self.__class__.__name__,
                                       location = location,
                                       angle=0,
                                       velocity=0,
                                       height=32.0,width=32.0,hitpoints=1,
                                       physical=False)))

    def defaultMessageAction(self,args):
        sentFrom, msg, msgArgs = args[0],args[1],args[2:]
        if msg == "WORLD_STATE":
            WorldState = msgArgs[0]
            if self.time == 0.0:
                self.time = WorldState.time + 0.5 # 启动时等待1/2秒
            elif WorldState.time >= self.time: # 每5秒
                self.time = WorldState.time + 5.0
            angle = random.random() * 360.0
            velocity = random.random() * 1000.0
            newRobot = random.choice(self.robots)
            newRobot(self.location,angle,velocity)

6.3.4 最终的模拟

最后,我们在世界的四角和中央各放置一个建造台。现在我们的模拟系统能够一直运行,并不断造出新的机器人。我们可以添加新的机器人种类,来随便摆弄这个系统。

6.4 总结

我们已经设法用少量的代码,建立了一个相当复杂的模拟系统,更重要的是,甚至每个角色都在独立运行。如果你将我们已经在传递的各个消息看作一个API,那它还真不是很复杂:

  • WORLD_STATE
  • JOIN
  • UPDATE_VECTOR
  • COLLISION
  • KILLME
  • DAMAGE

除此以外,一个角色所必需的其他所有内容,都被封装在其内部。为了了解外部的世界,它只需要处理这六条消息,这种机制不仅简化了程序本身,也简化了我们对其的理解过程。

7 完整代码列表

7.1 pingpong.py递归的乒乓球示例

def ping():
    print "PING"
    pong()

def pong():
    print "PONG"
    ping()

ping()

7.2 pingpong_stackless.py无堆栈的乒乓球示例

#
# pingpong_stackless.py
#

import stackless

ping_channel = stackless.channel()
pong_channel = stackless.channel()

def ping():
    while ping_channel.receive(): #blocks here
        print "PING"
        pong_channel.send("from ping")

def pong():
    while pong_channel.receive():
        print "PONG"
        ping_channel.send("from pong")

stackless.tasklet(ping)()
stackless.tasklet(pong)()

# we need to 'prime' the game by sending a start message
# if not, both tasklets will block
stackless.tasklet(ping_channel.send)('startup')

stackless.run()

7.3 hackysackthreaded.py基于操作系统线程的hackysack示例

import thread
import random
import sys
import Queue

class hackysacker:
    counter = 0
    def __init__(self,name,circle):
        self.name = name
        self.circle = circle
        circle.append(self)
        self.messageQueue = Queue.Queue()

        thread.start_new_thread(self.messageLoop,())

    def incrementCounter(self):
        hackysacker.counter += 1
        if hackysacker.counter >= turns:
            while self.circle:
                hs = self.circle.pop()
                if hs is not self:
                    hs.messageQueue.put('exit')
            sys.exit()

    def messageLoop(self):
        while 1:
            message = self.messageQueue.get()
            if message == "exit":
                debugPrint("%s is going home" % self.name)
                sys.exit()
            debugPrint("%s got hackeysack from %s" % (self.name, message.name))
            kickTo = self.circle[random.randint(0,len(self.circle)-1)]
            debugPrint("%s kicking hackeysack to %s" % (self.name, kickTo.name))
            self.incrementCounter()
            kickTo.messageQueue.put(self)

def debugPrint(x):
    if debug:
        print x

debug=1
hackysackers=5
turns = 5

def runit(hs=10,ts=10,dbg=1):
    global hackysackers,turns,debug
    hackysackers = hs
    turns = ts
    debug = dbg

    hackysacker.counter= 0
    circle = []
    one = hackysacker('1',circle)

    for i in range(hackysackers):
        hackysacker(`i`,circle)

    one.messageQueue.put(one)

    try:
        while circle:
            pass
    except:
        #sometimes we get a phantom error on cleanup.
        pass

if __name__ == "__main__":
    runit(dbg=1)

7.4 hackysackstackless.py stackless的hackysack示例

import stackless
import random
import sys

class hackysacker:
    counter = 0
    def __init__(self,name,circle):
        self.name = name
        self.circle = circle
        circle.append(self)
        self.channel = stackless.channel()

        stackless.tasklet(self.messageLoop)()

    def incrementCounter(self):
        hackysacker.counter += 1
        if hackysacker.counter >= turns:
            while self.circle:
                self.circle.pop().channel.send('exit')

    def messageLoop(self):
        while 1:
            message = self.channel.receive()
            if message == 'exit':
                return
            debugPrint("%s got hackeysack from %s" % (self.name, message.name))
            kickTo = self.circle[random.randint(0,len(self.circle)-1)]
            while kickTo is self:
                kickTo = self.circle[random.randint(0,len(self.circle)-1)]
            debugPrint("%s kicking hackeysack to %s" % (self.name, kickTo.name))
            self.incrementCounter()
            kickTo.channel.send(self)

def debugPrint(x):
    if debug:print x

debug = 5
hackysackers = 5
turns = 1

def runit(hs=5,ts=5,dbg=1):
    global hackysackers,turns,debug
    hackysackers = hs
    turns = ts
    debug = dbg

    hackysacker.counter = 0
    circle = []
    one = hackysacker('1',circle)

    for i in range(hackysackers):
        hackysacker(`i`,circle)

    one.channel.send(one)

    try:
        stackless.run()
    except TaskletExit:
        pass

if __name__ == "__main__":
    runit()

7.5 assemblyline.py普通的生产线示例

class storeroom:
    def __init__(self,name,product,unit,count):
        self.product = product
        self.unit = unit
        self.count = count
        self.name = name

    def get(self,count):
        if count > self.count:
            raise RuntimeError("Not enough %s" % self.product)
        else:
            self.count -= count
        return count

    def put(self,count):
        self.count += count

    def run(self):
        pass

rivetStoreroom = storeroom("rivetStoreroom","rivets","#",1000)
plasticStoreroom = storeroom("plastic Storeroom","plastic pellets","lb",100)

class injectionMolder:
    def __init__(self,name,partName,plasticSource,plasticPerPart,timeToMold):
        self.partName = partName
        self.plasticSource = plasticSource
        self.plasticPerPart = plasticPerPart
        self.timeToMold = timeToMold
        self.items = 0
        self.plastic = 0
        self.time = -1
        self.name = name

    def get(self,items):
        if items > self.items:
            return 0
        else:
            self.items -= items
            return items

    def run(self):
        if self.time == 0:
            self.items += 1
            print "%s finished making part" % self.name
            self.time -= 1
        elif self.time < 0:
            print "%s starts making new part %s" % (self.name,self.partName)
            if self.plastic < self.plasticPerPart:
                print "%s getting more plastic"
                self.plastic += self.plasticSource.get(self.plasticPerPart * 10)
            self.time = self.timeToMold
        else:
            print "%s molding for %s more seconds" % (self.partName, self.time)
            self.time -= 1

armMolder = injectionMolder("arm Molder", "arms",plasticStoreroom,0.2,6)
legMolder = injectionMolder("leg Molder", "leg",plasticStoreroom,0.2,5)
headMolder = injectionMolder("head Molder","head",plasticStoreroom,0.1,4)
torsoMolder = injectionMolder("torso Molder","torso",plasticStoreroom,0.5,10)

class assembler:
    def __init__(self,name,partAsource,partBsource,rivetSource,timeToAssemble):
        self.partAsource = partAsource
        self.partBsource = partBsource
        self.rivetSource = rivetSource
        self.timeToAssemble = timeToAssemble
        self.itemA = 0
        self.itemB = 0
        self.items = 0
        self.rivets = 0
        self.time = -1
        self.name = name

    def get(self,items):
        if items > self.items:
            return 0
        else:
            self.items -= items
            return items

    def run(self):
        if self.time == 0:
            self.items += 1
            print "%s finished assembling part" % self.name
            self.time -= 1
        elif self.time < 0:
            print "%s starts assembling new part" % self.name
            if self.itemA < 1:
                print "%s Getting item A" % self.name
                self.itemA += self.partAsource.get(1)
                if self.itemA < 1:
                    print "%s waiting for item A" % self.name
            elif self.itemB < 1:
                print "%s Getting item B" % self.name
                self.itemB += self.partBsource.get(1)
                if self.itemB < 1:
                    print "%s waiting for item B" % self.name
            print "%s starting to assemble" % self.name
            self.time = self.timeToAssemble
        else:
            print "%s assembling for %s more seconds" % (self.name, self.time)
            self.time -= 1

legAssembler = assembler("leg Assembler",torsoMolder,legMolder,rivetStoreroom,2)
armAssembler = assembler("arm Assembler", armMolder,legAssembler,rivetStoreroom,2)
torsoAssembler = assembler("torso Assembler", headMolder,armAssembler,
                        rivetStoreroom,3)

components = [rivetStoreroom, plasticStoreroom, armMolder,
              legMolder, headMolder, torsoMolder,
              legAssembler, armAssembler, torsoAssembler]

def run():
    while 1:
        for component in components:
            component.run()
        raw_input("Press <ENTER> to continue...")
        print "nnn"

if __name__ == "__main__":
    run()

7.6 assemblyline-stackless.py stackless的生产线示例

class storeroom:
    def __init__(self,name,product,unit,count):
        self.product = product
        self.unit = unit
        self.count = count
        self.name = name

    def get(self,count):
        if count > self.count:
            raise RuntimeError("Not enough %s" % self.product)
        else:
            self.count -= count
        return count

    def put(self,count):
        self.count += count

    def run(self):
        pass

rivetStoreroom = storeroom("rivetStoreroom","rivets","#",1000)
plasticStoreroom = storeroom("plastic Storeroom","plastic pellets","lb",100)

class injectionMolder:
    def __init__(self,name,partName,plasticSource,plasticPerPart,timeToMold):
        self.partName = partName
        self.plasticSource = plasticSource
        self.plasticPerPart = plasticPerPart
        self.timeToMold = timeToMold
        self.items = 0
        self.plastic = 0
        self.time = -1
        self.name = name

    def get(self,items):
        if items > self.items:
            return 0
        else:
            self.items -= items
            return items

    def run(self):
        if self.time == 0:
            self.items += 1
            print "%s finished making part" % self.name
            self.time -= 1
        elif self.time < 0:
            print "%s starts making new part %s" % (self.name,self.partName)
            if self.plastic < self.plasticPerPart:
                print "%s getting more plastic"
                self.plastic += self.plasticSource.get(self.plasticPerPart * 10)
            self.time = self.timeToMold
        else:
            print "%s molding for %s more seconds" % (self.partName, self.time)
    self.time -= 1

armMolder = injectionMolder("arm Molder", "arms",plasticStoreroom,0.2,6)
legMolder = injectionMolder("leg Molder", "leg",plasticStoreroom,0.2,5)
headMolder = injectionMolder("head Molder","head",plasticStoreroom,0.1,4)
torsoMolder = injectionMolder("torso Molder","torso",plasticStoreroom,0.5,10)

class assembler:
    def __init__(self,name,partAsource,partBsource,rivetSource,timeToAssemble):
        self.partAsource = partAsource
        self.partBsource = partBsource
        self.rivetSource = rivetSource
        self.timeToAssemble = timeToAssemble
        self.itemA = 0
        self.itemB = 0
        self.items = 0
        self.rivets = 0
        self.time = -1
        self.name = name

    def get(self,items):
        if items > self.items:
            return 0
        else:
            self.items -= items
            return items

    def run(self):
        if self.time == 0:
            self.items += 1
            print "%s finished assembling part" % self.name
            self.time -= 1
        elif self.time < 0:
            print "%s starts assembling new part" % self.name
            if self.itemA < 1:
                print "%s Getting item A" % self.name
                self.itemA += self.partAsource.get(1)
                if self.itemA < 1:
                    print "%s waiting for item A" % self.name
            elif self.itemB < 1:
                print "%s Getting item B" % self.name
                self.itemB += self.partBsource.get(1)
                if self.itemB < 1:
                    print "%s waiting for item B" % self.name
            print "%s starting to assemble" % self.name
            self.time = self.timeToAssemble
        else:
            print "%s assembling for %s more seconds" % (self.name, self.time)
    self.time -= 1

legAssembler = assembler("leg Assembler",torsoMolder,legMolder,rivetStoreroom,2)
armAssembler = assembler("arm Assembler", armMolder,legAssembler,rivetStoreroom,2)
torsoAssembler = assembler("torso Assembler", headMolder,armAssembler,
                            rivetStoreroom,3)

components = [rivetStoreroom, plasticStoreroom, armMolder,
          legMolder, headMolder, torsoMolder,
          legAssembler, armAssembler, torsoAssembler]

def run():
    while 1:
        for component in components:
            component.run()
        raw_input("Press <ENTER> to continue...")
        print "nnn"

if __name__ == "__main__":
    run()

7.7 digitalCircuit.py stackless的数字电路

import stackless

debug=0
def debugPrint(x):
    if debug:print x

class EventHandler:
    def __init__(self,*outputs):
        if outputs==None:
            self.outputs=[]
        else:
            self.outputs=list(outputs)

        self.channel = stackless.channel()
        stackless.tasklet(self.listen)()

    def listen(self):
        while 1:
            val = self.channel.receive()
            self.processMessage(val)
            for output in self.outputs:
                self.notify(output)

    def processMessage(self,val):
        pass

    def notify(self,output):
        pass

    def registerOutput(self,output):
        self.outputs.append(output)

    def __call__(self,val):
        self.channel.send(val)

class Switch(EventHandler):
    def __init__(self,initialState=0,*outputs):
        EventHandler.__init__(self,*outputs)
        self.state = initialState

    def processMessage(self,val):
        debugPrint("Setting input to %s" % val)
        self.state = val

    def notify(self,output):
        output((self,self.state))

class Reporter(EventHandler):
    def __init__(self,msg="%(sender)s send message %(value)s"):
        EventHandler.__init__(self)
        self.msg = msg

    def processMessage(self,msg):
        sender,value=msg
        print self.msg % {'sender':sender,'value':value}

class Inverter(EventHandler):
    def __init__(self,input,*outputs):
        EventHandler.__init__(self,*outputs)
        self.input = input
        input.registerOutput(self)
        self.state = 0

    def processMessage(self,msg):
        sender,value = msg
        debugPrint("Inverter received %s from %s" % (value,msg))
        if value:
            self.state = 0
        else:
            self.state = 1

    def notify(self,output):
        output((self,self.state))

class AndGate(EventHandler):
    def __init__(self,inputA,inputB,*outputs):
        EventHandler.__init__(self,*outputs)

        self.inputA = inputA
        self.inputAstate = inputA.state
        inputA.registerOutput(self)

        self.inputB = inputB
        self.inputBstate = inputB.state
        inputB.registerOutput(self)

        self.state = 0

    def processMessage(self,msg):
        sender, value = msg
        debugPrint("AndGate received %s from %s" % (value,sender))

        if sender is self.inputA:
            self.inputAstate = value
        elif sender is self.inputB:
            self.inputBstate = value
        else:
            raise RuntimeError("Didn't expect message from %s" % sender)

        if self.inputAstate and self.inputBstate:
            self.state = 1
        else:
            self.state = 0
        debugPrint("AndGate's new state => %s" % self.state)

    def notify(self,output):
        output((self,self.state))

class OrGate(EventHandler):
    def __init__(self,inputA,inputB,*outputs):
        EventHandler.__init__(self,*outputs)

        self.inputA = inputA
        self.inputAstate = inputA.state
        inputA.registerOutput(self)

        self.inputB = inputB
        self.inputBstate = inputB.state
        inputB.registerOutput(self)

        self.state = 0

    def processMessage(self,msg):
        sender, value = msg
        debugPrint("OrGate received %s from %s" % (value,sender))

        if sender is self.inputA:
            self.inputAstate = value
        elif sender is self.inputB:
            self.inputBstate = value
        else:
            raise RuntimeError("Didn't expect message from %s" % sender)

        if self.inputAstate or self.inputBstate:
            self.state = 1
        else:
            self.state = 0
        debugPrint("OrGate's new state => %s" % self.state)

    def notify(self,output):
        output((self,self.state))

if __name__ == "__main__":
    # half adder
    inputA = Switch()
    inputB = Switch()
    result = Reporter("Result = %(value)s")
    carry = Reporter("Carry = %(value)s")
    andGateA = AndGate(inputA,inputB,carry)
    orGate = OrGate(inputA,inputB)
    inverter = Inverter(andGateA)
    andGateB = AndGate(orGate,inverter,result)
    inputA(1)
    inputB(1)
    inputB(0)
    inputA(0)

7.8 actors.py第一个角色示例

import pygame
import pygame.locals
import os, sys
import stackless
import math

class actor:
    def __init__(self):
        self.channel = stackless.channel()
        self.processMessageMethod = self.defaultMessageAction
        stackless.tasklet(self.processMessage)()

    def processMessage(self):
        while 1:
            self.processMessageMethod(self.channel.receive())

    def defaultMessageAction(self,args):
        print args

class world(actor):
    def __init__(self):
        actor.__init__(self)
        self.registeredActors = {}
        stackless.tasklet(self.sendStateToActors)()

    def testForCollision(self,x,y):
        if x < 0 or x > 496:
            return 1
        elif y < 0 or y > 496:
            return 1
        else:
            return 0

    def sendStateToActors(self):
        while 1:
            for actor in self.registeredActors.keys():
                actorInfo = self.registeredActors[actor]
                if self.registeredActors[actor][1] != (-1,-1):
                    VectorX,VectorY = (math.sin(math.radians(actorInfo[2])) * actorInfo[3],
                                       math.cos(math.radians(actorInfo[2])) * actorInfo[3])
                    x,y = actorInfo[1]
                    x += VectorX
                    y -= VectorY
                    if self.testForCollision(x,y):
                        actor.send((self.channel,"COLLISION"))
                    else:
                        self.registeredActors[actor] = tuple([actorInfo[0],
                                                          (x,y),
                                                          actorInfo[2],
                                                          actorInfo[3]])
            worldState = [self.channel, "WORLD_STATE"]
            for actor in self.registeredActors.keys():
                if self.registeredActors[actor][1] != (-1,-1):
                    worldState.append( (actor, self.registeredActors[actor]))
            message = tuple(worldState)
            for actor in self.registeredActors.keys():
                actor.send(message)
            stackless.schedule()

    def defaultMessageAction(self,args):
        sentFrom, msg, msgArgs = args[0],args[1],args[2:]
        if msg == "JOIN":
            print 'ADDING ' , msgArgs
            self.registeredActors[sentFrom] = msgArgs
        elif msg == "UPDATE_VECTOR":
            self.registeredActors[sentFrom] = tuple([self.registeredActors[sentFrom][0],
                                                     self.registeredActors[sentFrom][1],
                                                     msgArgs[0],msgArgs[1]])
        else:
            print '!!!! WORLD GOT UNKNOWN MESSAGE ' , args

World = world().channel

class display(actor):
    def __init__(self,world=World):
        actor.__init__(self)

        self.world = World

        self.icons = {}
        pygame.init()

        window = pygame.display.set_mode((496,496))
        pygame.display.set_caption("Actor Demo")

        joinMsg = (self.channel,"JOIN",self.__class__.__name__, (-1,-1))
        self.world.send(joinMsg)

    def defaultMessageAction(self,args):
        sentFrom, msg, msgArgs = args[0],args[1],args[2:]
        if msg == "WORLD_STATE":
            self.updateDisplay(msgArgs)
        else:
            print "UNKNOWN MESSAGE", args

    def getIcon(self, iconName):
        if self.icons.has_key(iconName):
            return self.icons[iconName]
        else:
            iconFile = os.path.join("data","%s.bmp" % iconName)
            surface = pygame.image.load(iconFile)
            surface.set_colorkey((0xf3,0x0a,0x0a))
            self.icons[iconName] = surface
            return surface

    def updateDisplay(self,actors):
        for event in pygame.event.get():
            if event.type == pygame.QUIT: sys.exit()

        screen = pygame.display.get_surface()

        background = pygame.Surface(screen.get_size())
        background = background.convert()
        background.fill((200, 200, 200))

        screen.blit(background, (0,0))
        for item in actors:
            screen.blit(pygame.transform.rotate(self.getIcon(item[1][0]),-item[1][2]), item[1][1])
        pygame.display.flip()

display()

class basicRobot(actor):
    def __init__(self,location=(0,0),angle=135,velocity=1,world=World):
        actor.__init__(self)
        self.location = location
        self.angle = angle
        self.velocity = velocity
        self.world = world

        joinMsg =(self.channel,"JOIN",self.__class__.__name__,
                  self.location,self.angle,self.velocity)
        self.world.send(joinMsg)

    def defaultMessageAction(self,args):
        sentFrom, msg, msgArgs = args[0],args[1],args[2:]
        if msg == "WORLD_STATE":
            self.location = (self.location[0] + 1, self.location[1] + 1)
            self.angle += 1
            if self.angle >= 360:
                self.angle -= 360

            updateMsg = (self.channel, "UPDATE_VECTOR",
                         self.angle,self.velocity)
            self.world.send(updateMsg)
        elif msg == "COLLISION":
            self.angle += 73
            if self.angle >= 360:
                self.angle -= 360
        else:
            print "UNKNOWN MESSAGE", args

basicRobot(angle=135,velocity=5)
basicRobot((464,0),angle=225,velocity=10)

stackless.run()

7.9 actors2.py第二个角色示例

import pygame
import pygame.locals
import os, sys
import stackless
import math
import time

class actor:
    def __init__(self):
        self.channel = stackless.channel()
        self.processMessageMethod = self.defaultMessageAction
        stackless.tasklet(self.processMessage)()

    def processMessage(self):
        while 1:
            self.processMessageMethod(self.channel.receive())

    def defaultMessageAction(self,args):
        print args

class properties:
    def __init__(self,name,location=(-1,-1),angle=0,
                 velocity=0,height=-1,width=-1,hitpoints=1,physical=True,
                 public=True):
        self.name = name
        self.location = location
        self.angle = angle
        self.velocity = velocity
        self.height = height
        self.width = width
        self.public = public
        self.hitpoints = hitpoints
        self.physical = physical

class worldState:
    def __init__(self,updateRate,time):
        self.updateRate = updateRate
        self.time = time
        self.actors = []

class world(actor):
    def __init__(self):
        actor.__init__(self)
        self.registeredActors = {}
        self.updateRate = 30
        self.maxupdateRate = 30
        stackless.tasklet(self.runFrame)()

    def testForCollision(self,x,y,item,otherItems=[]):
        if x < 0 or x + item.width > 496:
            return self.channel
        elif y < 0 or y+ item.height > 496:
            return self.channel
        else:
            ax1,ax2,ay1,ay2 = x, x+item.width, y,y+item.height
            for item,bx1,bx2,by1,by2 in otherItems:
                if self.registeredActors[item].physical == False: continue
                for x,y in [(ax1,ay1),(ax1,ay2),(ax2,ay1),(ax2,ay2)]:
                    if x >= bx1 and x <= bx2 and y >= by1 and y <= by2:
                        return item
                for x,y in [(bx1,by1),(bx1,by2),(bx2,by1),(bx2,by2)]:
                    if x >= ax1 and x <= ax2 and y >= ay1 and y <= ay2:
                        return item
            return None

    def killDeadActors(self):
        for actor in self.registeredActors.keys():
            if self.registeredActors[actor].hitpoints <= 0:
                print "ACTOR DIED", self.registeredActors[actor].hitpoints
                actor.send_exception(TaskletExit)
                del self.registeredActors[actor]

    def updateActorPositions(self):
        actorPositions = []
        for actor in self.registeredActors.keys():
            actorInfo = self.registeredActors[actor]
            if actorInfo.public and actorInfo.physical:
                x,y = actorInfo.location
                angle = actorInfo.angle
                velocity = actorInfo.velocity
                VectorX,VectorY = (math.sin(math.radians(angle)) * velocity,
                                   math.cos(math.radians(angle)) * velocity)
                x += VectorX/self.updateRate
                y -= VectorY/self.updateRate
                collision = self.testForCollision(x,y,actorInfo,actorPositions)
                if collision:
                    #don't move
                    actor.send((self.channel,"COLLISION",actor,collision))
                    if collision and collision is not self.channel:
                        collision.send((self.channel,"COLLISION",actor,collision))
                else:
                    actorInfo.location = (x,y)
                actorPositions.append( (actor,
                                        actorInfo.location[0],
                                        actorInfo.location[0] + actorInfo.height,
                                        actorInfo.location[1],
                                        actorInfo.location[1] + actorInfo.width))

    def sendStateToActors(self,starttime):
        WorldState = worldState(self.updateRate,starttime)
        for actor in self.registeredActors.keys():
            if self.registeredActors[actor].public:
                WorldState.actors.append( (actor, self.registeredActors[actor]) )
        for actor in self.registeredActors.keys():
            actor.send( (self.channel,"WORLD_STATE",WorldState) )

    def runFrame(self):
        initialStartTime = time.clock()
        startTime = time.clock()
        while 1:
            self.killDeadActors()
            self.updateActorPositions()
            self.sendStateToActors(startTime)
            #wait
            calculatedEndTime = startTime + 1.0/self.updateRate

            doneProcessingTime = time.clock()
            percentUtilized =  (doneProcessingTime - startTime) / (1.0/self.updateRate)
            if percentUtilized >= 1:
                self.updateRate -= 1
                print "TOO MUCH LOWERING FRAME RATE: " , self.updateRate
            elif percentUtilized <= 0.6 and self.updateRate < self.maxupdateRate:
                self.updateRate += 1
                print "TOO MUCH FREETIME, RAISING FRAME RATE: " , self.updateRate

            while time.clock() < calculatedEndTime:
                stackless.schedule()
            startTime = calculatedEndTime

            stackless.schedule()

    def defaultMessageAction(self,args):
        sentFrom, msg, msgArgs = args[0],args[1],args[2:]
        if msg == "JOIN":
            print 'ADDING ' , msgArgs
            self.registeredActors[sentFrom] = msgArgs[0]
        elif msg == "UPDATE_VECTOR":
            self.registeredActors[sentFrom].angle = msgArgs[0]
            self.registeredActors[sentFrom].velocity = msgArgs[1]
        elif msg == "COLLISION":
            pass # known, but we don't do anything
        elif msg == "KILLME":
            self.registeredActors[sentFrom].hitpoints = 0
        else:
            print '!!!! WORLD GOT UNKNOWN MESSAGE ' , args

World = world().channel

class display(actor):
    def __init__(self,world=World):
        actor.__init__(self)

        self.world = World
        self.icons = {}
        pygame.init()

        window = pygame.display.set_mode((496,496))
        pygame.display.set_caption("Actor Demo")

        self.world.send((self.channel,"JOIN",
                            properties(self.__class__.__name__,
                                       public=False)))

    def defaultMessageAction(self,args):
        sentFrom, msg, msgArgs = args[0],args[1],args[2:]
        if msg == "WORLD_STATE":
            self.updateDisplay(msgArgs)
        else:
            print "UNKNOWN MESSAGE", args

    def getIcon(self, iconName):
        if self.icons.has_key(iconName):
            return self.icons[iconName]
        else:
            iconFile = os.path.join("data","%s.bmp" % iconName)
            surface = pygame.image.load(iconFile)
            surface.set_colorkey((0xf3,0x0a,0x0a))
            self.icons[iconName] = surface
            return surface

    def updateDisplay(self,msgArgs):

        for event in pygame.event.get():
            if event.type == pygame.QUIT: sys.exit()

        screen = pygame.display.get_surface()

        background = pygame.Surface(screen.get_size())
        background = background.convert()
        background.fill((200, 200, 200))

        screen.blit(background, (0,0))

        WorldState = msgArgs[0]

        for channel,item in WorldState.actors:
            screen.blit(pygame.transform.rotate(self.getIcon(item.name),-item.angle), item.location)
        pygame.display.flip()

display()

class basicRobot(actor):
    def __init__(self,location=(0,0),angle=135,velocity=1,
                 hitpoints=20,world=World):
        actor.__init__(self)
        self.location = location
        self.angle = angle
        self.velocity = velocity
        self.hitpoints = hitpoints
        self.world = world
        self.world.send((self.channel,"JOIN",
                            properties(self.__class__.__name__,
                                       location=self.location,
                                       angle=self.angle,
                                       velocity=self.velocity,
                                       height=32,width=32,
                                       hitpoints=self.hitpoints)))

    def defaultMessageAction(self,args):
        sentFrom, msg, msgArgs = args[0],args[1],args[2:]
        if msg == "WORLD_STATE":
            for actor in msgArgs[0].actors:
                if actor[0] is self: break
            self.location = actor[1].location
            self.angle += 30.0 * (1.0 / msgArgs[0].updateRate)
            if self.angle >= 360:
                self.angle -= 360

            updateMsg = (self.channel, "UPDATE_VECTOR", self.angle,
                         self.velocity)
            self.world.send(updateMsg)
        elif msg == "COLLISION":
            self.angle += 73.0
            if self.angle >= 360:
                self.angle -= 360
            self.hitpoints -= 1
            if self.hitpoints <= 0:
                self.world.send((self.channel, "KILLME"))
        elif msg == "DAMAGE":
            self.hitpoints -= msgArgs[0]
            if self.hitpoints <= 0:
                self.world.send( (self.channel,"KILLME") )
        else:
            print "UNKNOWN MESSAGE", args

basicRobot(angle=135,velocity=150)
basicRobot((464,0),angle=225,velocity=300)
basicRobot((100,200),angle=78,velocity=500)
basicRobot((400,300),angle=298,velocity=5)
basicRobot((55,55),angle=135,velocity=150)
basicRobot((464,123),angle=225,velocity=300)
basicRobot((180,200),angle=78,velocity=500)
basicRobot((400,380),angle=298,velocity=5)

stackless.run()

7.10 actors3.py第三个角色示例

import pygame
import pygame.locals
import os, sys
import stackless
import math
import time
import random

class actor:
    def __init__(self):
        self.channel = stackless.channel()
        self.processMessageMethod = self.defaultMessageAction
        stackless.tasklet(self.processMessage)()

    def processMessage(self):
        while 1:
            self.processMessageMethod(self.channel.receive())

    def defaultMessageAction(self,args):
        print args

class properties:
    def __init__(self,name,location=(-1,-1),angle=0,
                 velocity=0,height=-1,width=-1,hitpoints=1,physical=True,
                 public=True):
        self.name = name
        self.location = location
        self.angle = angle
        self.velocity = velocity
        self.height = height
        self.width = width
        self.public = public
        self.hitpoints = hitpoints
        self.physical = physical

class worldState:
    def __init__(self,updateRate,time):
        self.updateRate = updateRate
        self.time = time
        self.actors = []

class world(actor):
    def __init__(self):
        actor.__init__(self)
        self.registeredActors = {}
        self.updateRate = 30
        self.maxupdateRate = 30
        stackless.tasklet(self.runFrame)()

    def testForCollision(self,x,y,item,otherItems=[]):
        if x < 0 or x + item.width > 496:
            return self.channel
        elif y < 0 or y+ item.height > 496:
            return self.channel
        else:
            ax1,ax2,ay1,ay2 = x, x+item.width, y,y+item.height
            for item,bx1,bx2,by1,by2 in otherItems:
                if self.registeredActors[item].physical == False: continue
                for x,y in [(ax1,ay1),(ax1,ay2),(ax2,ay1),(ax2,ay2)]:
                    if x >= bx1 and x <= bx2 and y >= by1 and y <= by2:
                        return item
                for x,y in [(bx1,by1),(bx1,by2),(bx2,by1),(bx2,by2)]:
                    if x >= ax1 and x <= ax2 and y >= ay1 and y <= ay2:
                        return item
            return None

    def killDeadActors(self):
        for actor in self.registeredActors.keys():
            if self.registeredActors[actor].hitpoints <= 0:
                print "ACTOR DIED", self.registeredActors[actor].hitpoints
                actor.send_exception(TaskletExit)
                del self.registeredActors[actor]

    def updateActorPositions(self):
        actorPositions = []
        for actor in self.registeredActors.keys():
            actorInfo = self.registeredActors[actor]
            if actorInfo.public and actorInfo.physical:
                x,y = actorInfo.location
                angle = actorInfo.angle
                velocity = actorInfo.velocity
                VectorX,VectorY = (math.sin(math.radians(angle)) * velocity,
                                   math.cos(math.radians(angle)) * velocity)
                x += VectorX/self.updateRate
                y -= VectorY/self.updateRate
                collision = self.testForCollision(x,y,actorInfo,actorPositions)
                if collision:
                    #don't move
                    actor.send((self.channel,"COLLISION",actor,collision))
                    if collision and collision is not self.channel:
                        collision.send((self.channel,"COLLISION",actor,collision))
                else:
                    actorInfo.location = (x,y)
                actorPositions.append( (actor,
                                        actorInfo.location[0],
                                        actorInfo.location[0] + actorInfo.height,
                                        actorInfo.location[1],
                                        actorInfo.location[1] + actorInfo.width))

    def sendStateToActors(self,starttime):
        WorldState = worldState(self.updateRate,starttime)
        for actor in self.registeredActors.keys():
            if self.registeredActors[actor].public:
                WorldState.actors.append( (actor, self.registeredActors[actor]) )
        for actor in self.registeredActors.keys():
            actor.send( (self.channel,"WORLD_STATE",WorldState) )

    def runFrame(self):
        initialStartTime = time.clock()
        startTime = time.clock()
        while 1:
            self.killDeadActors()
            self.updateActorPositions()
            self.sendStateToActors(startTime)
            #wait
            calculatedEndTime = startTime + 1.0/self.updateRate

            doneProcessingTime = time.clock()
            percentUtilized =  (doneProcessingTime - startTime) / (1.0/self.updateRate)
            if percentUtilized >= 1:
                self.updateRate -= 1
                print "TOO MUCH LOWERING FRAME RATE: " , self.updateRate
            elif percentUtilized <= 0.6 and self.updateRate < self.maxupdateRate:
                self.updateRate += 1
                print "TOO MUCH FREETIME, RAISING FRAME RATE: " , self.updateRate

            while time.clock() < calculatedEndTime:
                stackless.schedule()
            startTime = calculatedEndTime
            stackless.schedule()

    def defaultMessageAction(self,args):
        sentFrom, msg, msgArgs = args[0],args[1],args[2:]
        if msg == "JOIN":
            self.registeredActors[sentFrom] = msgArgs[0]
        elif msg == "UPDATE_VECTOR":
            self.registeredActors[sentFrom].angle = msgArgs[0]
            self.registeredActors[sentFrom].velocity = msgArgs[1]
        elif msg == "COLLISION":
            pass # known, but we don't do anything
        elif msg == "KILLME":
            self.registeredActors[sentFrom].hitpoints = 0
        else:
            print '!!!! WORLD GOT UNKNOWN MESSAGE ' , msg, msgArgs

World = world().channel

class display(actor):
    def __init__(self,world=World):
        actor.__init__(self)

        self.world = World
        self.icons = {}
        pygame.init()

        window = pygame.display.set_mode((496,496))
        pygame.display.set_caption("Actor Demo")

        self.world.send((self.channel,"JOIN",
                            properties(self.__class__.__name__,
                                       public=False)))

    def defaultMessageAction(self,args):
        sentFrom, msg, msgArgs = args[0],args[1],args[2:]
        if msg == "WORLD_STATE":
            self.updateDisplay(msgArgs)
        else:
            print "DISPLAY UNKNOWN MESSAGE", args

    def getIcon(self, iconName):
        if self.icons.has_key(iconName):
            return self.icons[iconName]
        else:
            iconFile = os.path.join("data","%s.bmp" % iconName)
            surface = pygame.image.load(iconFile)
            surface.set_colorkey((0xf3,0x0a,0x0a))
            self.icons[iconName] = surface
            return surface

    def updateDisplay(self,msgArgs):

        for event in pygame.event.get():
            if event.type == pygame.QUIT: sys.exit()

        screen = pygame.display.get_surface()

        background = pygame.Surface(screen.get_size())
        background = background.convert()
        background.fill((200, 200, 200))

            screen.blit(background, (0,0))

            WorldState = msgArgs[0]

            for channel,item in WorldState.actors:
                itemImage = self.getIcon(item.name)
                itemImage = pygame.transform.rotate(itemImage,-item.angle)
                screen.blit(itemImage, item.location)
            pygame.display.flip()

    display()

    class basicRobot(actor):
        def __init__(self,location=(0,0),angle=135,velocity=1,
                     hitpoints=20,world=World):
            actor.__init__(self)
            self.location = location
            self.angle = angle
            self.velocity = velocity
            self.hitpoints = hitpoints
            self.world = world
            self.world.send((self.channel,"JOIN",
                                properties(self.__class__.__name__,
                                           location=self.location,
                                           angle=self.angle,
                                           velocity=self.velocity,
                                               height=32,width=32,hitpoints=self.hitpoints)))

            def defaultMessageAction(self,args):
                sentFrom, msg, msgArgs = args[0],args[1],args[2:]
                if msg == "WORLD_STATE":
                    for actor in msgArgs[0].actors:
                        if actor[0] is self: break
                    self.location = actor[1].location
                    self.angle += 30.0 * (1.0 / msgArgs[0].updateRate)
                    if self.angle >= 360:
                        self.angle -= 360

                    updateMsg = (self.channel, "UPDATE_VECTOR",
                                 self.angle,self.velocity)
                    self.world.send(updateMsg)
                elif msg == "COLLISION":
                    self.angle += 73.0
                    if self.angle >= 360:
                        self.angle -= 360
                    self.hitpoints -= 1
                    if self.hitpoints <= 0:
                        explosion(self.location,self.angle)
                        self.world.send((self.channel, "KILLME"))
                elif msg == "DAMAGE":
                    self.hitpoints -= msgArgs[0]
                    if self.hitpoints <= 0:
                        explosion(self.location,self.angle)
                        self.world.send( (self.channel,"KILLME") )

                else:
                    print "BASIC ROBOT UNKNOWN MESSAGE", args

        class explosion(actor):
            def __init__(self,location=(0,0),angle=0,world=World):
                actor.__init__(self)
                self.time = 0.0
                self.world = world
                self.world.send((self.channel,"JOIN",
                                    properties(self.__class__.__name__,
                                               location = location,
                                               angle = angle,
                                               velocity=0,
                                               height=32.0,width=32.0,hitpoints=1,
                                               physical=False)))

            def defaultMessageAction(self,args):
                sentFrom, msg, msgArgs = args[0],args[1],args[2:]
                if msg == "WORLD_STATE":
                    WorldState = msgArgs[0]
                    if self.time == 0.0:
                        self.time = WorldState.time
                    elif WorldState.time >= self.time + 3.0:
                        self.world.send( (self.channel, "KILLME") )

        class mine(actor):
            def __init__(self,location=(0,0),world=World):
                actor.__init__(self)
                self.world = world
                self.world.send((self.channel,"JOIN",
                                    properties(self.__class__.__name__,
                                               location=location,
                                               angle=0,
                                               velocity=0,
                                               height=2.0,width=2.0,hitpoints=1)))

            def defaultMessageAction(self,args):
                sentFrom, msg, msgArgs = args[0],args[1],args[2:]
                if msg == "WORLD_STATE":
                    pass
                elif msg == "COLLISION":
                    if msgArgs[0] is self.channel:
                        other = msgArgs[1]
                    else:
                        other = msgArgs[0]
                    other.send( (self.channel,"DAMAGE",25) )
                    self.world.send( (self.channel,"KILLME"))
                    print "MINE COLLISION"
                else:
                    print "UNKNOWN MESSAGE", args

        class minedropperRobot(actor):
            def __init__(self,location=(0,0),angle=135,velocity=1,
                         hitpoints=20,world=World):
                actor.__init__(self)
                self.location = location
                self.angle = angle
                self.delta = 0.0
                self.height=32.0
                self.width=32.0
                self.deltaDirection = "up"
                self.nextMine = 0.0
                self.velocity = velocity
                self.hitpoints = hitpoints
                self.world = world
                self.world.send((self.channel,"JOIN",
                                    properties(self.__class__.__name__,
                                               location=self.location,
                                               angle=self.angle,
                                               velocity=self.velocity,
                                               height=self.height,width=self.width,
                                               hitpoints=self.hitpoints)))

            def defaultMessageAction(self,args):
                sentFrom, msg, msgArgs = args[0],args[1],args[2:]
                if msg == "WORLD_STATE":
                    for actor in msgArgs[0].actors:
                        if actor[0] is self.channel:
                            break
                    self.location = actor[1].location
                    if self.deltaDirection == "up":
                        self.delta += 60.0 * (1.0 / msgArgs[0].updateRate)
                        if self.delta > 15.0:
                            self.delta = 15.0
                            self.deltaDirection = "down"
                    else:
                        self.delta -= 60.0 * (1.0 / msgArgs[0].updateRate)
                        if self.delta < -15.0:
                            self.delta = -15.0
                            self.deltaDirection = "up"
                    if self.nextMine <= msgArgs[0].time:
                        self.nextMine = msgArgs[0].time + 1.0
                        mineX,mineY = (self.location[0] + (self.width / 2.0) ,
                                       self.location[1] + (self.width / 2.0))

                        mineDistance = (self.width / 2.0 ) ** 2
                        mineDistance += (self.height / 2.0) ** 2
                        mineDistance = math.sqrt(mineDistance)

                        VectorX,VectorY = (math.sin(math.radians(self.angle + self.delta)),
                                           math.cos(math.radians(self.angle + self.delta)))
                        VectorX,VectorY = VectorX * mineDistance,VectorY * mineDistance
                        x,y = self.location
                        x += self.width / 2.0
                        y += self.height / 2.0
                        x -= VectorX
                        y += VectorY
                        mine( (x,y))
                    updateMsg = (self.channel, "UPDATE_VECTOR",
                                 self.angle + self.delta ,self.velocity)
                    self.world.send(updateMsg)
                elif msg == "COLLISION":
                    self.angle += 73.0
                    if self.angle >= 360:
                        self.angle -= 360
                    self.hitpoints -= 1
                    if self.hitpoints <= 0:
                        explosion(self.location,self.angle)
                        self.world.send((self.channel,"KILLME"))
                elif msg == "DAMAGE":
                    self.hitpoints -= msgArgs[0]
                    if self.hitpoints <= 0:
                        explosion(self.location,self.angle)
                        self.world.send((self.channel, "KILLME"))
                else:
                    print "UNKNOWN MESSAGE", args

        class spawner(actor):
            def __init__(self,location=(0,0),world=World):
                actor.__init__(self)
                self.location = location
                self.time = 0.0
                self.world = world

                self.robots = []
                for name,klass in globals().iteritems():
                    if name.endswith("Robot"):
                        self.robots.append(klass)

                self.world.send((self.channel,"JOIN",
                                    properties(self.__class__.__name__,
                                               location = location,
                                               angle=0,
                                               velocity=0,
                                               height=32.0,width=32.0,hitpoints=1,
                                               physical=False)))

            def defaultMessageAction(self,args):
                sentFrom, msg, msgArgs = args[0],args[1],args[2:]
                if msg == "WORLD_STATE":
                    WorldState = msgArgs[0]
                    if self.time == 0.0:
                        self.time = WorldState.time + 0.5 # wait 1/2 second on start
                    elif WorldState.time >= self.time: # every five seconds
                        self.time = WorldState.time + 5.0
                        angle = random.random() * 360.0
                        velocity = random.random() * 1000.0
                        newRobot = random.choice(self.robots)
                        newRobot(self.location,angle,velocity)

        spawner( (32,32) )
        spawner( (432,32) )
        spawner( (32,432) )
        spawner( (432,432) )
        spawner( (232,232) )

        stackless.run()

8 引用链接

stackless

9 给jorge的reStructuredText示例

一段文字

另外一段文字,注意段之间要有空行。

加重的字体使用 两个星号之间 ,当然星号与外界要留有空格。

引用的文字用两个那个符号,比如 我是引用文字 ,同样边界要有空格。这种引用一般用于代码引用,当然这是风格问题,希望你可以与我的风格相同。而下面的反引号引用可以由于函数定义等等非语句性质的引用。至于星号形成的斜体,我一般不用的。

斜体字就比较好说了,单个星号或单个的反引号都是,比如 斜体 或 斜体 。当然,比较规范的斜体是使用星号的。反引号一般用于引用。

链接么就比较简单了,比如 惊帆之静默 注意反引号结束之后有个下划线。然后定义链接地址在下一行。这里的反引号引用用于链接名称里面含有中文和空格的情况,如果没有这些,则可以省略反引号。

关于标题,只要拥有相同下划线的标题就属于同一个级别,当然,上面已经很多例子了。标题与文字段之间也有空行。

代码引用就很酷了,就是在一行的末尾用双冒号,如:

我是代码
def hello():
    print 'hello'
    return

当然,代码所在的块需要缩进表示,结束就是使用原缩进 即可。

图片的引用方式如下所示。

http://图片地址好了就这些吧,高级功能你用到时再问我,比如文章引用,表格等等。

Zenoss快速指导

Monday, November 5th, 2007

Zenoss快速指导

原文标题: Zenoss Quick Start Guide
原文日期: 2007-9-26
对应版本: Zenoss 2.0
翻译: gashero
翻译日期: 2007-10-31

目录

1 概览

本文档的目标是让你了解并可以安装和运行zenoss,并了解如下任务:

  • 安装Zenoss Core软件
  • 自定义环境
  • 探索设备
  • 监控环境
  • 管理环境

1.1 Zenoss Core简介

Zenoss Core被下载次数最多的开源企业级IT管理软件茶品-是智能监控软件,他允许IT管理员依靠单一的WEB控制台来监控网络架构的状态和健康度。Zenoss Core同时也是开源的网络与系统管理软件,知识产权归Zenoss公司所有,地址在美国马里兰州的安娜波利斯。

Zenoss Core的强大能力来自于深入的列表与配置管理数据库,以发现和管理公司IT环境的各类资产(包括服务器、网络、和其他结构设备)。Zenoss可以创建关键资产清单和对应的组件级别(接口、服务、进程,已安装的软件等)。建立好模型后,就可以监控与报告IT架构中各种资源的状态和性能容限了。Zenoss同时提供与CMDB关联的事件和错误管理系统,以协助提高各类事件和提醒的管理效率。以此提高IT管理人员的效率。

1.2 附加资源

如果需要附加帮助,或者有问题,可以查看Zenoss的网站:

如果有关于本文档的其他问题,可以发邮件询问:

feedback@zenoss.com

2 入门

Zenoss Core可以通过Zenoss的网站来下载:

http://www.zenoss.com/download

或者从Zenoss的Sourceforge项目处下载:

http://sourceforge.net/projects/zenoss

Zenoss Core可以运行于大多数Linux和Unix操作系统,不过,我们推荐运行于Red Hat Enterprise Linux(RHEL)平台或其他任何RHEL派生平台,如Centos或Fedora。

Zenoss Core同时提供虚拟机版本,可以运行于Windows和Linux平台,简单的通过VMware Player即可实现。

Zenoss Core是一个监控系统的解决方案,使用浏览器来使用。支持的WEB浏览器包括 FireFoxInternet Explorer 7 。Zenoss Core部分的支持 SafariOpera 浏览器。

2.1 以RPM方式安装Zenoss Core

已经安装完成了,略……

2.2 以源码方式安装Zenoss Core

已经安装完成了,略……

2.3 通过VM Player使用Zenoss Core

已经安装完成了,略……

2.4 登入并使用

在浏览器中输入如下地址以访问:

http://xxx.xxx.xxx.xxx:8080

IP地址要换成你安装Zenoss的机器,如果是通过VM方式,IP地址也要指到虚拟机的IP。

如果全部是默认安装,则默认的登录过程为:

用户名: admin

密码: zenoss

点击 Submit

3 定义环境

Zenoss Core是完整的企业级监控解决方案,允许你高效的管理与监控IT架构。没有两种环境是一样的,Zenoss允许你定义自己选择的环境。如下是配置的入门。

3.1 简单设置

3.1.1 配置SMTP服务器

下面是配置SMTP服务器的简单步骤:

  1. 从左侧导航菜单点击 Settings
  2. Settings 标签页中选择 “SMTP Host” 以选择需要使用的SMTP服务器
  3. 设置 “SMTP User” 和 “SMTP Password” 字段,当然,如果需要的话
  4. 点击 Save 按钮

Note

缺省的设置指本机的SMTP服务器,通过Settings页面还可以配置其他很多东西,如SNMP主机等。

3.1.2 配置SNMP组字符串

SNMP组字符串配置在 ZProperties 页面的 “zSnmpCommunity” 参数。这些可以在设备对象树中配置。也就是指定设备的 ZProperties 页面的’ZSnmpCommunity’字段。

对于自动发现设备,Zenoss提供了在你的环境中自动发现所有有效的组字符串的能力,并可以利用这些来发现网络和实体环境建模。

下面的步骤简单的讲解了配置SNMP组列表:

  1. 从左侧导航菜单中选择 Devices
  2. 选择 zProperties 页面
  3. 添加有效的SNMP字符串到 ‘ZSnmpCommunities’ 文本框

Note

修改后的’zSnmpCommunities’ 参数会被继承。

Warning

现在这个选项在Devices->zProperties页面,中下部。版本2.1.0。

3.1.3 创建Windows管理员设置

如果希望Zenoss能够访问和控制Windows WMI框架,则Zenoss服务器必须拥有Windows设备的管理员级别的权限。下面是设置Windows管理员配置的基本步骤:

  1. 从左侧导航菜单中选择 Devices
  2. 下拉选择 Server 链接,然后点击 Windows 链接以打开Windows设备类别页
  3. Windows 类别页面,点击 zProperties
  4. zProperties 页,设置 “zWinUser” 字段,缺省为 “Administrator”
  5. 同时设置 “zWinPassword” 字段

Note

修改 “zWinUser” 和 “zWinPassword” 参数可以对任何级别的设备起效,并且这个参数可以被子设备继承。

Warning

注意,这个选项现在在Devices->zProperties里面,靠下。版本2.1.0。

3.2 创建逻辑组

可以配置逻辑组,以管理不同的环境视图。创建的逻辑组的类型完全依赖喜好,不过一般是分组和子组以方便理解IT架构为好。

3.2.1 创建位置

如下展示了创建位置组和子组的简单步骤:

  1. 从左侧导航菜单选择 Locations
  2. 点击下拉表格菜单选择 Sub-Locations ,然后选择 “Add New Organizer”
  3. 填写新的位置的ID字段
  4. 点击OK以保存 Location

这些步骤可以用于创建已有 LocationSub-Location ,步骤是类似的。

3.2.2 创建系统

下面展示了建立组以后需要做的步骤:

  1. 左侧导航菜单点击 System
  2. 点击下拉表格菜单选择 Sub-Systems 表格并选择 “Add New Organizer”
  3. 填写新的位置的ID字段
  4. 点击OK以保存 Systems

这些步骤对于建立已有系统组的子组也是一样的。

3.2.3 创建自己的组

下面展示了建立位置组或子组的步骤:

  1. 左侧导航菜单中点击 Groups
  2. 点击下拉表格菜单选择 Sub-Groups 表格并选择 “Add New Organizer”
  3. 添加新的位置ID字段
  4. 点击OK以保存 Groups

这些步骤对于建立已有组的子位置也是一样的。

3.3 定义监控进程

Zenoss允许自定义通过网络要监控的进程。在读进程建模时,Zenoss会检查定义的进程列表,对比运行在目标资源上面的进程,并自动建模和监控设备上的指定进程。下面是建立进程的简单步骤:

  1. 左侧导航菜单点击 Process
  2. 点击下拉表格菜单选择 Processes 表格并选择 “Add Process”
  3. 填写新的进程ID字段
  4. 点击OK保存新的网络地址

Note

正则表达式可以应用于指定的进程标识符,并且zenoss将会使用它去匹配。

4 了解环境

4.1 添加设备

设备可以单独添加到Zenoss监控,下面的简单步骤介绍了如何通过SNMP对一个设备建模。

  1. 从左侧导航菜单选择 Add a Device
  2. Add a Device 页面的 ‘Device Name’ 字段填入需要被监控机器的IP地址或者域名
  3. 设置 ‘SNMP Community’ 字段,如果需要的话
  4. 从 ‘Device Class Path’ 下拉菜单中,选择设备类别,比如’/Server/Linux’
  5. 填入’Location Path’字段、’Systems’字段等,如果需要分组还需要’Groups’字段
  6. 点击 ‘Add device’ 添加到自动发现模型的设备

建模过程之后,就可以通过链接导航到设备。

4.2 添加网络

你可以添加一个 Network (网络)到Zenoss系统中。下面的步骤:

  1. 从左侧导航菜单选择 Networks
  2. Subnetwork 页面的 Overview 页面点击下拉表格菜单选择 ‘Add Network’
  3. 添加新的网络地址和掩码,以 CIDR 格式,例如192.168.1.0/24 到’ID’字段
  4. 点击’OK’按钮来保存新的网络地址

Note

这些步骤可以用于任意层次的网络树的子网络。

4.3 探索网络

可以使用Zenoss自带的网络探索功能自动发现网络设备,使用下面的步骤:

  1. 在导航菜单中选择 Networks
  2. 点击下拉表格菜单中的 Sub-networks 选择’Discover Devices’

Note

这些步骤可以用于自动发现各个层次的网络树。

5 监控环境

5.1 查看管理资源

通过Zenoss Core,可以通过系统来查看所有被监控的资源。最简单的方法是通过 Device 链接。而 Device List 表格提供了一个基础架构中所有设备的完整表格,并包含简单的附加信息。

点击设备名 Device Id 列,可以以系那是所选设备的状态 Status 页面。

也可以在页面右上角以 Device/IP Search 来搜索特定的设备。

5.2 查看事件

可以通过 Event Console 来查看IT环境发生的所有事件。通过左侧导航菜单的 Events Console 来打开。

事件控制台提供了IT架构中发生的所有活跃事件。你可以使用事件控制台实现:

  • 查看详细的事件信息
  • 关于事件的附加知识
  • 配置简单或高级的报警策略
  • 更新事件修正日志
  • 分类与关联事件
  • 备份事件到历史数据库

5.3 查看报告

可以通过Zenoss生成预定义或自定义的报告,通过左侧导航菜单的 Reports 。Zenoss预定义报告提供了工业标准的报告以显示网络健康状态统计和环境的SLA。

5.4 自定义监控

Zenoss可以通过自定义的 ZenCommand 来收集信息,支持SSH、XML-RPC在内的多种协议。你可以通过在 Zenoss box 中的脚本执行自定义监控远程设备,当然也有自定义的报告。Zenoss正在开发的 ZenPack 提供了几种监控方式以提供自定义监控。更多的信息请关注 Zenoss Administrator Guide 。

5.4.1 添加ZenPack

Zenoss认证的ZenPack提供了使用Zenoss Core的自定义监控。下面是使用这些自定义监控的简单步骤。在这个实例中讲解HTTPMonitor的监控。

  1. 从服务器下载HttpMonitor ZenPack: http://www.zenoss.com/download/zenpacks/HttpMonitor.zip
  2. 到服务器上找到下载的zenpack
  3. 以zenoss用户登录,执行安装zenpack的命令,注意xxx为例子的文件名:
    zenossserver ~# zenpack run -install HttpMonitor.zip
  4. 创建一些数据源以供自定义监控,详细参考 Zenoss Administartor Guide

6 管理环境

6.1 添加Zenoss用户

Zenoss允许按照角色创建用户,以访问Zenoss系统。Zenoss也支持定义事件跟踪,以提供更多的高级提示与报警功能,并且还区分角色。

6.1.1 添加一个Zenoss用户

按照下面的步骤:

  1. 左侧导航菜单,选择 Settings
  2. 点击 Users 页面显示 User Folder 列表
  3. 点击下拉表格菜单,选择 ‘Add New User’
  4. 在 ‘Username’ 字段,输入新的用户名,然后添加email地址到 ‘Email’ 字段
  5. 点击 ‘OK’ 以保存新的用户配置
  6. User Folder 表格,以 ‘UserId’ 列点击用户链接以修改更多配置
  7. Edit 页面的 ‘Password’ 字段修改密码
  8. 在 ‘Roles’ 选择框选择用户的角色

Note

用户名必须是全部小写,而且不可以有空格。

6.1.2 创建报警规则

下面步骤展示了如何创建报警规则:

  1. User Folder 表格,点击需要编辑的用户链接
  2. 点击 Alerting Rules 页面显示 Alerting Rules 列表
  3. 点击下拉表格菜单并选择 ‘Add New Rule’
  4. 在 ‘ID’ 字段输入新的规则名
  5. 点击 ‘OK’ 保存新的报警规则
  6. Alerting Rule 表格,点击刚刚建立的规则名以修改附加信息
  7. Edit 页面,改变 ‘Enabled’ 下拉菜单为True
  8. 通过 ‘Action’ 来改变报警模式,email或page
  9. 通过 ‘Where’ 这个下拉菜单中的参数来改变发送到各个用户的警报的过滤器。缺省的条件是立即发送高于系统定义级别的警报,你可以用这个来指定广播警报还是发到指定的人

6.1.3 创建警报规则定期发送

按照如下步骤:

  1. 从用户的 Alerting Rule 表格,按照 ‘Name’ 点击进入规则的修改
  2. 点击 Schedule 页面显示 Active Periods 表格(有效的时间段)
  3. 点击下拉表格菜单并选择 ‘Add Rule Window’
  4. 添加新的规则名,并填入 ‘ID’ 字段
  5. 点击 ‘OK’ 按钮保存有效时间段
  6. 在有效时间段表格,点击新的规则窗口链接,按照 ‘Name’ 列,并修改详细信息
  7. Status 页面,改变 ‘Enabled’ 下拉菜单为 True
  8. 配置开始时间 ‘Start’
  9. 配置持续时间 ‘Duration’
  10. 设置重现规则时间 ‘Repeat’

7 注释

无……

做大事与项目管理

Thursday, November 1st, 2007

做大事与项目管理

最近听了一位同事的项目管理讲座,实际上主要还是讲Project 2007的使用。软件的使用当然枯燥乏味,转身即忘,不过对Project的管理功能还是有些感触的。

在使用了一些项目管理软件以后,确实可以达到对项目进程和各类资源的精确控制,而这种控制,就是降低风险的好方法。

大学刚毕业时(注:其实刚毕业1年),每每递上简历,都要申明我在某某技术上的熟悉程度,我做过什么样的程序。现在看来与公司真正的最需求的还是有相当的差距。对于一个金字塔状的公司结构来说,一个人的价值在于他可以做多大的事情。如果只是关注于某些技术细节,那么可能控制的范围就比较有限。如果可以从多个方面对公司需要的“事”能够精确的掌控和实现,那就价值很大了。

说到头来,也是这种金字塔形式的层次,实际上对老板来说,他最期望的莫过于什么都不干,只要把投资交给雇员,雇员就可以源源不断的生产出钱来。但是现实世界没有这么理想,所以在整个体系中,有不同的人做着不同的事情。有人可以把一个方面做的精益求精,有些人就善于协调,有些善于交流。当然,从老板的角度讲,还是那句,如果一个雇员可以协调所有资源,并最终创造价值,那么这个雇员的身价也会高很多。

比如某个产品,甚至仅仅是老板说:我想要业绩增长的一句话。一个雇员可以立即提出方案和规划,资源配置,时间安排,那么这个雇员的价值就会立即体现出来。

所以,总的来说么,做老板的大多不懂技术,既然不懂就不必跟老板多说,把他能懂的事情做好,就是本分。当然,这里就涉及到很多其他的事情了,如协调、资源配置等等。但只要把事情做好就是了。

从编程的角度讲,就是提供高层次的封装,隐藏底层细节。至于底层实现就可以有很多学问了,善于协调的可以找别人做,善于实现的可以多受累一些自己做,总之实现了高层次封装和隐藏底层细节就是了。

不过貌似国内有一些很不好的风气,就是鄙视做技术的,而且国内技术人员的待遇也远远无法与管理层相比。也许高层次的协调是一种很稀缺的能力吧,但是却不应该由此形成等级观念。况且世界这么复杂,每个人虽然能力略有差异,但是也不至于就肯定不会某些技能。绝对的做管理和绝对的做技术是不存在的。当然,互相批评的主要目标指的就是这些绝对的人。

总之呢,我以后要学一些关于项目管理的知识了。做技术很享受,但是享受的同时,如果辅以管理,并最终提高生活品质就更好了。