找到目录 gevent 的文章 2 篇.


Gevent的hub剖析

Posted on 2014-02-13 16:41:10 gevent

摘要:
  • hub是gevent中核心,依靠libev这个事件库,来调度所有的greenlet。

  • hub也是一个greenlet,就是所谓的’main’ greenlet。

  • hub会首先启动,然后马上启动事件循环。也就是libev的loop。

  • hub对应到实际的代码就是hub.py,其中最关键的是Hub类。

  • hub保存在线程的本地数据中,所以说每个线程中只存在一个’main’ hub。

  • 从main greenlet切换到普通greenlet之后,main greenlet是停止执行的。greenlet是安排合理的串行,从而看起来像是并行。

  • 切换到main greenlet是指首先切换到hub.wait,然后在greenlet.so中执行真正的greenlet切换。 即切换到需要运行的那个greenlet。

  • 所谓的’由hub决定运行哪个greenlet’,实际上是loop检测到greenlet感兴趣的事件后,首先切换到hub,再由hub.switch切换到发生事件的那个greenlet。主导整个过程的是libev的loop。

阅读全文

关于Gevent的那些事儿

Posted on 2014-01-22 12:35:42 gevent

1. spawn一个greenlet之后,不会立即运行。

除非有隐式或者显示的手段switch到这个greenlet。

2. gevent.spawn做了什么?

只是创建了一个greenlet实例,并加入到了greenlet链,还没有运行它。

3. greenlet.switch到底做了什么?

greenlet.switch最终会调用greenlet.c中的C函数。 greenlet.c中g_swith会扫描greenlet链,如果发现没有启动的greenlet,则调用g_initialstub启动它。 实际上g_initialstub会在greenlet类实例中查找run属性,找到了则运行它。 如果greenlet是已经启动的,则switch到main hub.

4. g=gevent.spawn(func); g.run()做了什么?

按照3的说法,它首先创建了greenlet实例,设置了实例的’run’属性。 然后直接运行刚才指定的’run’属性,也可以叫它callback,是一个callable对象。

5. 当创建多个greenlet实例,然后并不run他们。

最后再启动一个greenlet,为什么之前创建的greenlet还是可以运行?

因为最后运行的那个greenlet中,发生了switch,对应到greenlet.c中的g_swith。 它会扫描greenlet链,如果发现没有启动的greenlet则启动它们。 如果最后运行的那个greenlet中没有发生switch,则在它之前创建的greenlet无法运行。

例如下面的代码:

def g1():
    print 'g1'
    gevent.sleep(2)

def g2():
    print 'g2'
    gevent.sleep(2)

def g3():
    print 'g3'                                                                                                                    
    gevent.sleep(1)

gevent.spawn(g1)
gevent.spawn(g2)
gevent.spawn(g3).run()

输出:

g3
g1
g2

如果函数g3,没有最后一行gevent.sleep(1), 则g1和g2都不会执行 只是输出’g3’字符串之后,进程退出。

6. hub.Waiter类有什么作用?

Waiter用来在greenlet之间通信,它简单包装了greenlet.swith。 典型的工作过程如下:

>>> result = Waiter() #1
>>> timer = get_hub().loop.timer(0.1) #2
>>> timer.start(result.switch, 'hello from Waiter') #3
>>> result.get() #4
'hello from Waiter'
  • #1: 创建一个Waiter类实例,在init中什么也不做。

  • #2: 启动一个libev的watcher(libev有许多watcher例如io/timer/signal等,可以理解为对特定对象的#监视对象#)

  • #3: 为这个#监视对象#设置callback和参数,这里的是Waiter实例的switch函数,参数为字符串。 接着立即让libev开始监视这个对象。 timer.start只是让libev启动一个time类型的watcher,并在事件发生后,执行回调。 waiter.switch的代码只有关键的一行:greenlet.switch(value),它切换当前协程到main hub. 但是watcher并没有执行result.switch,只是将它加入了libev的callback列表。 要等待libev.loop.run启动主循环。 记住,这里给libev的watcher传递了一个callback,并带有参数。

  • #4: 这里很关键, waiter.get中有self.hub.switch(),表示立即出让时间片到main hub. 流程到了hub.switch这里,它调用greenlet.switch(self)。 这里的greenlet是C代码中的greenlet类实例,是所有greenlet的父类。 所以我们到了greenlet.so模块内部。

    注意这时,虽然main hub已经被创建了,但是从来没有运行过。 所以greenlet.c中switch会调用g_initialstub执行main hub的run函数,这里会启动core.so->loop.run,启动libev的 ioloop.

    因为#3给libev的callback中增加了watcher(waiter.switch),所以ioloop启动后会检测watcher事件,事件发生后调用watcher的callback.

    就会调用waiter.switch,其中一行代码:self.greenlet.switch(value) 因为我们一直在main hub中,所以又回到了hub.switch,在hub.switch中是return greenlet.switch(self),所以这里会返回。 流程最后又回到了wait.get中,其中returen hub.switch()返回最终的值。

7. gevent.sleep到底做了什么?

结合Waiter是如何工作的,再看sleep的代码:

def sleep(seconds=0, ref=True):
    # 得到main hub
    hub = get_hub()

    # 得到hub.loop,这是libev的主循环
    loop = hub.loop

    # 如果sleep时间小于等于0
    # 启动一个waiter,直接调用loop的run_callback运行waiter.switch
    # 再调用waiter.get()会立即返回
    # 因为我们没有给core.so->loop传递任何watcher,所以会立即返回
    if seconds <= 0:
        waiter = Waiter()
        loop.run_callback(waiter.switch)
        waiter.get()
    else:
        # 这里启动了一个libev的timer类型的watcher
        # 然后传递给hub.wait
        # hub.wait的代码,是Waiter类的典型用法:
        # waiter = Waiter()
        # unique = object()
        # watcher.start(waiter.switch, unique)
        # try:
        #      result = waiter.get()
        #      assert result is unique, 'Invalid switch'
        # finally:
        #      watcher.stop()
        # 启动一个timer类型的watcher,
        # 在waiter.get中切换到main hub,然后在事件发生后调用waiter.switch
        # 在waiter.switch中,执行self.greenlet.switch,控制流又会回到waiter.get中
        # 因为self.greenlet是调用waiter.get的协程,也就是调用gevent.sleep的协程
        hub.wait(loop.timer(seconds, ref=ref))
8. monkey.patch_xxx很邪恶
  • 首先monkey.patch_xxx只需要执行一次即可,即在main中执行.

  • 其次monkey.patch_all会给所有模块做patch. 解决办法是,只使用特定模块的patch,如monkey.patch_socket

  • 最后monkey.patch_xxx会给所有导入的模块做patch 解决办法是在其他需要导入的模块执行下面的操作:

    import a
    import socket
    reload(socket)
    print socket.socket     # <class 'socket._socketobject'>