Archive for December, 2008

winpcap with libevent failed!

Saturday, December 27th, 2008

libevent简短笔记

作者: gashero

目录

1   简介

使用libevent的简短笔记,加上我自己积累的技巧。

2   初始化

2.1   头文件与编译标志

头文件 event.h

编译标志 -levent 。在Windows下因为用到了socket函数,所以要同时加上 -lwsock32

在MinGW下编译时可以用 sh configure --prefix=/c/other_c_lib 这类方式来指定安装路径,然后可以安装过去,更方便。

2.2   建立event句柄

初始化 libevent 可用函数 event_init() 和 event_base_new() 。

实用 event_set() 来初始化事件结构体,然后用 event_add() 添加到事件循环。然后用 event_dispatch() 来启动事件循环和发布事件。

基本不周流程:

struct event ev; event_init(); event_set(&ev,s,EV_READ|EV_PERSIST,callback,&ev); event_add(&ev,NULL); event_dispatch();

注意要在callback函数中的耗时处理之前再次调用 event_add() 再次加入事件循环。所以需要使用arg来传递ev对象过来,有时候如果需要传递的东西过多,就需要一个结构体。

2.3   函数声明格式

struct event_base* event_init() :初始化事件API。

void event_set(struct event* ev, int fd, short events, void(*fn)(int, short, void*), void* arg) :构造一个事件结构用于准备添加。ev为事件句柄,fd为文件描述符,events为几种事件的组合,然后是回调函数和附加传输参数。可用的events组合包括: EV_TIMEOUT 、 EV_READ 、 EV_WRITE 、 EV_SIGNAL 、 EV_PERSIST 。fn回调函数的第一个参数为文件描述符,第二个是事件常量数字,第三个为传递过来的附加参数。

#define EV_TIMEOUT      0x01 #define EV_READ         0x02 #define EV_WRITE        0x04 #define EV_SIGNAL       0x08 #define EV_PERSIST      0x10

int event_add(struct event* ev, struct timeval* tv) :添加事件到监控器,可以指定超时为参数tv。

int event_del(struct event* ev) :删除事件。

2.4   超时事件

事件的超时功能是在 event_add() 函数中的第二个参数指定的。是一个timeval结构体。当tv参数为NULL时就没有超时。而当传递进去timeval结构体时就可以提供理论上高达1微秒的精度的超时。这里一个超时示例:

struct timeval tv; tv.tv_sec=1; tv.tv_usec=0; event_add(&ev,tv);

超时发生时可以在回调函数fn中的event参数得到值为EV_TIMEOUT的事件,注意不要与其他类型事件混淆。

2.5   初始化小心

注意声明的 struct event 结构体实例的作用域,在 main() 函数或者全局的尚且没事。如果只是在某个函数内部声明,则出函数后该结构体被回收则会出错。最好将这个结构体放在堆里面:

struct event* ev_accept=(struct event*)malloc(sizeof(struct event));

3   回调函数

回调函数声明格式:

void fn(int fd, short event, void* arg);

其中fd为文件句柄,可以按照特定的环境使用不同的读写函数。event为事件类型,见如上定义的5种事件类型中的前4种。

注意需要在回调函数中再次调用该事件的 event_add() 函数来确保事件继续下去。所以至少需要使用arg来传递ev结构体,如果需要传递的东西很多,则可能需要用arg来传递一个自定义的结构体,包含多个结构体的指针。

4   IO缓存

5   MinGW下的使用

一直没能正常启动,调试中。现在是用1.4.9。 event_dispatch() 函数出错返回-1。

event_dispatch()

int event_dispatch(void) {     return (event_loop(0)); }

event_loop(int flags)

int event_loop(int flags) {     return event_base_loop(current_base, flags); }

event_base_loop(struct event_base *base, int flags)

int event_base_loop(struct event_base *base, int flags) { }

调试发现 event_base_loop() 函数中有两处可能返回-1。加上打印调试以后发现是来自 event.c:533 处返回的。其调用了函数 evsel->dispatch() 所致。

全局变量 current_base 的流程:

/* Global state */ struct event_base *current_base=NULL;  struct event_base *event_init(void) {     struct event_base *base=event_base_new();     if (base != NULL)         current_base=base;     return (base); }

返回-1的是 res=evsel->dispatch(base,evbase,tv_p) 。实际相当于 current_base->evsel->dispatch(current_base,current_base->evbase,tv_p) 。经查其通过最终调用的结构体是 win32ops 。跟踪到被调用的实际上是 WIN32-Code/win32.c:345 中的 win32_dispatch() 函数。

win32_dispatch() 中的-1来自于 select() 的返回。使用了 GetLastError() 得到了错误码10038,大概意思是”在非socket上执行操作”。原来Windows下的 select() 真的不支持文件描述符的操作。那就算了吧。晚上试试加IOCP插件。

纪念学习Python三周年

Friday, December 26th, 2008

2005年圣诞节的夜晚,快要考试了,有些压抑,于是翻翻电脑上的书,找到了这个Python,于是一发不可收拾。现在已经做了2年多Python程序员了。世事难料。

greenlet:轻量级并发编程

Wednesday, December 3rd, 2008

greenlet:轻量级并发编程

译者: gashero

目录

1   动机

greenlet 包是 Stackless 的副产品,其将微线程称为 “tasklet” 。tasklet运行在伪并发中,使用channel进行同步数据交换。

一个”greenlet”,是一个更加原始的微线程的概念,但是没有调度,或者叫做协程。这在你需要控制你的代码时很有用。你可以自己构造微线程的 调度器;也可以使用”greenlet”实现高级的控制流。例如可以重新创建构造器;不同于Python的构造器,我们的构造器可以嵌套的调用函数,而被 嵌套的函数也可以 yield 一个值。(另外,你并不需要一个”yield”关键字,参考例子)。

Greenlet是作为一个C扩展模块给未修改的解释器的。

1.1   例子

假设系统是被控制台程序控制的,由用户输入命令。假设输入是一个个字符的。这样的系统有如如下的样子:

def process_commands(*args):
    while True:
        line=''
        while not line.endswith('\n'):
            line+=read_next_char()
        if line=='quit\n':
            print "are you sure?"
            if read_next_char()!="y":
                continue    #忽略指令
        process_commands(line)

现在假设你要把程序移植到GUI,而大多数GUI是事件驱动的。他们会在每次的用户输入时调用回调函数。这种情况下,就很难实现 read_next_char() 函数。我们有两个不兼容的函数:

def event_keydown(key):
    ??

def read_next_char():
    ?? 需要等待 event_keydown() 的调用

你可能在考虑用线程实现。而 Greenlet 是另一种解决方案,没有锁和关闭问题。你启动 process_commands() 函数,分割成 greenlet ,然后与按键事件交互,有如:

def event_keydown(key):
    g_processor.switch(key)

def read_next_char():
    g_self=greenlet.getcurrent()
    next_char=g_self.parent.switch()    #跳到上一层(main)的greenlet,等待下一次按键
    return next_char

g_processor=greenlet(process_commands)
g_processor.switch(*args)
gui.mainloop()

这个例子的执行流程是: read_next_char() 被调用,也就是 g_processor 的一部分,它就会切换(switch)到他的父greenlet,并假设继续在顶级主循环中执行(GUI主循环)。当GUI调用 event_keydown() 时,它切换到 g_processor ,这意味着执行会跳回到原来挂起的地方,也就是 read_next_char() 函数中的切换指令那里。然后 event_keydown() 的 key 参数就会被传递到 read_next_char() 的切换处,并返回。

注意 read_next_char() 会被挂起并假设其调用栈会在恢复时保护的很好,所以他会在被调用的地方返回。这允许程序逻辑保持优美的顺序流。我们无需重写 process_commands() 来用到一个状态机中。

2   使用

2.1   简介

一个 “greenlet” 是一个很小的独立微线程。可以把它想像成一个堆栈帧,栈底是初始调用,而栈顶是当前greenlet的暂停位置。你使用greenlet创建一堆这样的堆 栈,然后在他们之间跳转执行。跳转不是绝对的:一个greenlet必须选择跳转到选择好的另一个greenlet,这会让前一个挂起,而后一个恢复。两 个greenlet之间的跳转称为 切换(switch)

当你创建一个greenlet,它得到一个初始化过的空堆栈;当你第一次切换到它,他会启动指定的函数,然后切换跳出greenlet。当最终栈底 函数结束时,greenlet的堆栈又编程空的了,而greenlet也就死掉了。greenlet也会因为一个未捕捉的异常死掉。

例如:

from py.magic import greenlet

def test1():
    print 12
    gr2.switch()
    print 34

def test2():
    print 56
    gr1.switch()
    print 78

gr1=greenlet(test1)
gr2=greenlet(test2)
gr1.switch()

最后一行跳转到 test1() ,它打印12,然后跳转到 test2() ,打印56,然后跳转回 test1() ,打印34,然后 test1() 就结束,gr1死掉。这时执行会回到原来的 gr1.switch() 调用。注意,78是不会被打印的。

2.2   父greenlet

现在看看一个greenlet死掉时执行点去哪里。每个greenlet拥有一个父greenlet。父greenlet在每个greenlet初 始化时被创建(不过可以在任何时候改变)。父greenlet是当greenlet死掉时,继续原来的位置执行。这样,greenlet就被组织成一棵 树,顶级的代码并不在用户创建的 greenlet 中运行,而称为主greenlet,也就是树根。

在上面的例子中,gr1和gr2都是把主greenlet作为父greenlet的。任何一个死掉,执行点都会回到主函数。

未捕获的异常会波及到父greenlet。如果上面的 test2() 包含一个打印错误(typo),他会生成一个 NameError 而干掉gr2,然后执行点会回到主函数。traceback会显示 test2() 而不是 test1() 。记住,切换不是调用,但是执行点可以在并行的栈容器间并行交换,而父greenlet定义了栈最初从哪里来。

2.3   实例

py.magic.greenlet 是一个 greenlet 类型,支持如下操作:

greenlet(run=None,parent=None)

创建一个greenlet对象,而不执行。run是执行回调,而parent是父greenlet,缺省是当前greenlet。

greenlet.getcurrent()

返回当前greenlet,也就是谁在调用这个函数。

greenlet.GreenletExit

这个特定的异常不会波及到父greenlet,它用于干掉一个greenlet。

greenlet 类型可以被继承。一个greenlet通过调用其 run 属性执行,就是创建时指定的那个。对于子类,可以定义一个 run() 方法,而不必严格遵守在构造器中给出 run 参数。

2.4   切换

greenlet之间的切换发生在greenlet的 switch() 方法被调用时,这会让执行点跳转到greenlet的 switch() 被调用处。或者在greenlet死掉时,跳转到父greenlet那里去。在切换时,一个对象或异常被发送到目标greenlet。这可以作为两个greenlet之间传递信息的方便方式。例如:

def test1(x,y):
    z=gr2.switch(x+y)
    print z

def test2(u):
    print u
    gr1.switch(42)

gr1=greenlet(test1)
gr2=greenlet(test2)
gr1.switch("hello"," world")

这会打印出 “hello world” 和42,跟前面的例子的输出顺序相同。注意 test1() 和 test2() 的参数并不是在 greenlet 创建时指定的,而是在第一次切换到这里时传递的。

这里是精确的调用方式:

g.switch(obj=None or *args)

切换到执行点greenlet g,发送给定的对象obj。在特殊情况下,如果g还没有启动,就会让它启动;这种情况下,会传递参数过去,然后调用 g.run(*args)

垂死的greenlet

如果一个greenlet的 run() 结束了,他会返回值到父greenlet。如果 run() 是异常终止的,异常会波及到父greenlet(除非是 greenlet.GreenletExit 异常,这种情况下异常会被捕捉并返回到父greenlet)。

除了上面的情况外,目标greenlet会接收到发送来的对象作为 switch() 的返回值。虽然 switch() 并不会立即返回,但是它仍然会在未来某一点上返回,当其他greenlet切换回来时。当这发生时,执行点恢复到 switch() 之后,而 switch() 返回刚才调用者发送来的对象。这意味着 x=g.switch(y) 会发送对象y到g,然后等着一个不知道是谁发来的对象,并在这里返回给x。

注意,任何尝试切换到死掉的greenlet的行为都会切换到死掉greenlet的父greenlet,或者父的父,等等。最终的父就是 main greenlet,永远不会死掉的。

2.5   greenlet的方法和属性

g.switch(obj=None or *args)

切换执行点到greenlet g,同上。

g.run

调用可执行的g,并启动。在g启动后,这个属性就不再存在了。

g.parent

greenlet的父。这是可写的,但是不允许创建循环的父关系。

g.gr_frame

当前顶级帧,或者None。

g.dead

判断是否已经死掉了

bool(g)

如果g是活跃的则返回True,在尚未启动或者结束后返回False。

g.throw([typ,[val,[tb]]])

切换执行点到greenlet g,但是立即抛出指定的异常到g。如果没有提供参数,异常缺省就是 greenlet.GreenletExit 。根据异常波及规则,有如上面描述的。注意调用这个方法等同于如下:

def raiser():
    raise typ,val,tb

g_raiser=greenlet(raiser,parent=g)
g_raiser.switch()

2.6   Greenlet与Python线程

greenlet可以与Python线程一起使用;在这种情况下,每个线程包含一个独立的 main greenlet,并拥有自己的greenlet树。不同线程之间不可以互相切换greenlet。

2.7   活动greenlet的垃圾收集

如果不再有对greenlet对象的引用时(包括其他greenlet的parent),还是没有办法切换回greenlet。这种情况下会生成一个 GreenletExit 异常到greenlet。这是greenlet收到异步异常的唯一情况。应该给出一个 try .. finally 用于清理greenlet内的资源。这个功能同时允许greenlet中无限循环的编程风格。这样循环可以在最后一个引用消失时自动中断。

如果不希望greenlet死掉或者把引用放到别处,只需要捕捉和忽略 GreenletExit 异常即可。

greenlet不参与垃圾收集;greenlet帧的循环引用数据会被检测到。将引用传递到其他的循环greenlet会引起内存泄露。