[gevent源码分析] 深度分析gevent运行流程

http://blog.csdn.net/yueguanghaidao/article/details/24281751

一直对gevent运行流程比较模糊,最近看源码略有所得,不敢独享,故分享之。

gevent是一个高性能网络库,底层是libevent,1.0版本之后是libev,核心是greenlet。gevent和eventlet是亲近,唯一不同的是eventlet是自己实现的事件驱动,而gevent是使用libev。两者都有广泛的应用,如openstack底层网络通信使用eventlet,goagent是使用gevent。

 
要想理解gevent首先要理解gevent的调度流程,gevent中有一个hub的概念,也就是下图的MainThread,用于调度所有其它的greenlet实例(下图Coroutine)。
其实hub也是一个greenlet,只不过特殊一些。
看下图我们会发现每次从hub切换到一个greenlet后,都会回到hub,这就是gevent的关键。
注意:gevent中并没有greenlet链的说法,所有都是向主循环注册greenlet.switch方法,主循环在合适的时机切换回来。

也许大家会好奇,为什么采用这种模式,为什么每次都要切换到hub?我想理由有二:

1.hub是事件驱动的核心,每次切换到hub后将继续循环事件。如果在一个greenlet中不出来,那么其它greenlet将得不到调用。

2.维持两者关系肯定比维持多个关系简单。每次我们所关心的就是hub以及当前greenlet,不需要考虑各个greenlet之间关系。

我们看看最简单的gevent.sleep发生了什么?

我们先想想最简单的sleep(0)该如何调度?根据上面很明显

1.向事件循环注册当前greenlet的switch函数

2.切换到hub,运行主事件循环

  1. def sleep(seconds=0, ref=True):
  2. hub = get_hub()
  3. loop = hub.loop
  4. if seconds <= 0:
  5. waiter = Waiter()
  6. loop.run_callback(waiter.switch)
  7. waiter.get()
  8. else:
  9. hub.wait(loop.timer(seconds, ref=ref))

当seconds小于等于0时,loop.run_callback(waiter.switch)即是将当前greenlet的switch注册到loop,使用waiter.get()切换到hub。那么很明显,

当切换到hub后当调用刚注册的回调(waiter.switch)回到刚刚sleep所在的greenlet。

不熟悉Waiter的童鞋可能对上面说的有点模糊,下面我们好好看看Waiter是什么。

  1. >>> result = Waiter()
  2. >>> timer = get_hub().loop.timer(0.1)
  3. >>> timer.start(result.switch, 'hello from Waiter')
  4. >>> result.get() # blocks for 0.1 seconds
  5. 'hello from Waiter'

timer.start(result.switch, 'hello from Waiter')我们向hub的主循环注册一个0.1s的定时器,回调为result.switch,然后将执行result.get(),此时过程代码如下:

  1. def get(self):
  2. assert self.greenlet is None, 'This Waiter is already used by %r' % (self.greenlet, )
  3. self.greenlet = getcurrent()
  4. try:
  5. return self.hub.switch()
  6. finally:
  7. self.greenlet = None

将把self.greenlet设置为当前greenlet,然后通过self.hub.switch()切换到主循环,很明显在主循环中将回调result.switch,看代码:

  1. def switch(self, value=None):
  2. """Switch to the greenlet if one's available. Otherwise store the value."""
  3. greenlet = self.greenlet
  4. assert getcurrent() is self.hub, "Can only use Waiter.switch method from the Hub greenlet"
  5. switch = greenlet.switch
  6. try:
  7. switch(value)
  8. except:
  9. self.hub.handle_error(switch, *sys.exc_info())

拿到刚保存的greenlet,然后切换到greenlet.switch(),返回到我们刚调用reuslt.get()方法。通过上面assert我们也可以看出这是在hub中调用的。

通过以上分析,小伙伴们肯定都懂了gevent的执行流程了。

这里有个问题,如果上面先发生result.switch,那又该如何呢?就像下面这样:

  1. >>> result = Waiter()
  2. >>> timer = get_hub().loop.timer(0.1)
  3. >>> timer.start(result.switch, 'hi from Waiter')
  4. >>> sleep(0.2)
  5. >>> result.get() # returns immediatelly without blocking
  6. 'hi from Waiter'

我想聪明的你,打开hub.py再看看源码肯定就明白了(上面Waiter代码是我特意简化的)。

既然我们知道了gevent运行流程,下面我们看看gevent.spawn和join到底做了什么?

gevent.spawn其实就是Greenlet.spawn,所以gevent.spawn就是创建一个greenlet,并将该greenlet的switch()加入hub主循环回调。

  1. class Greenlet(greenlet):
  2. """A light-weight cooperatively-scheduled execution unit."""
  3. def __init__(self, run=None, *args, **kwargs):
  4. hub = get_hub()
  5. greenlet.__init__(self, parent=hub)
  6. if run is not None:
  7. self._run = run
  8. self._start_event = None
  9. def start(self):
  10. """Schedule the greenlet to run in this loop iteration"""
  11. if self._start_event is None:
  12. self._start_event = self.parent.loop.run_callback(self.switch)
  13. @classmethod
  14. def spawn(cls, *args, **kwargs):
  15. """Return a new :class:`Greenlet` object, scheduled to start.
  16. The arguments are passed to :meth:`Greenlet.__init__`.
  17. """
  18. g = cls(*args, **kwargs)
  19. g.start()
  20. return g

通过下面代码证明:

  1. import gevent
  2. def talk(msg):
  3. print(msg)
  4. g1 = gevent.spawn(talk, 'bar')
  5. gevent.sleep(0)

将输出:bar,我们通过sleep切换到hub,然后hub将运行我们添加的回调talk,一切正常。

此时不要沾沾自喜,如果下面代码也觉得一切正常再高兴也不迟。

  1. import gevent
  2. def talk(msg):
  3. print(msg)
  4. gevent.sleep(0)
  5. print msg
  6. g1 = gevent.spawn(talk, 'bar')
  7. gevent.sleep(0)

这次还是输出:bar,有点不对劲啊,应该输出两个bar才对,为什么为导致这样呢?

我们来好好分析流程:

1.gevent.spawn注册回调talk

2.然后最后一行gevent.sleep(0)注册当前greenlet.switch(最外面的)到hub,然后切换到hub

3.hub执行回调talk,打印"bar",此时gevent.sleep再次将g1.switch注册到hub,同时切换到hub

4.由于第2步最外层greenlet现注册,所以将调用最外层greenlet,此时很明显,程序将结束。因为最外层greenlet并不是hub的子greenlet,

所以died后并不会回到父greenlet,即hub

你可能会说那我自己手动切换到hub不就可以了吗?这将导致主循环结束不了的问题。

  1. import gevent
  2. def talk(msg):
  3. print(msg)
  4. gevent.sleep(0)
  5. print msg
  6. g1 = gevent.spawn(talk, 'bar')
  7. gevent.get_hub().switch()

程序输出:

  1. bar
  2. bar
  3. Traceback (most recent call last):
  4. File "F:\py_cgi\geve.py", line 9, in <module>
  5. gevent.get_hub().switch()
  6. File "C:\Python26\lib\site-packages\gevent\hub.py", line 331, in switch
  7. return greenlet.switch(self)
  8. gevent.hub.LoopExit: This operation would block forever

虽然成功的输出了两次“bar",但也导致了更为严重的问题。

这也就是join存在的价值,我们看看join是如何做到的?

  1. def join(self, timeout=None):
  2. """Wait until the greenlet finishes or *timeout* expires.
  3. Return ``None`` regardless.
  4. """
  5. if self.ready():
  6. return
  7. else:
  8. switch = getcurrent().switch
  9. self.rawlink(switch)
  10. try:
  11. t = Timeout.start_new(timeout)
  12. try:
  13. result = self.parent.switch()
  14. assert result is self, 'Invalid switch into Greenlet.join(): %r' % (result, )
  15. finally:
  16. t.cancel()
  17. except Timeout:
  18. self.unlink(switch)
  19. if sys.exc_info()[1] is not t:
  20. raise
  21. except:
  22. self.unlink(switch)
  23. raise
  24. def rawlink(self, callback):
  25. """Register a callable to be executed when the greenlet finishes the execution.
  26. WARNING: the callable will be called in the HUB greenlet.
  27. """
  28. if not callable(callback):
  29. raise TypeError('Expected callable: %r' % (callback, ))
  30. self._links.append(callback)
  31. if self.ready() and self._links and not self._notifier:
  32. self._notifier = self.parent.loop.run_callback(self._notify_links)
  33. def _notify_links(self):
  34. while self._links:
  35. link = self._links.popleft()
  36. try:
  37. link(self)
  38. except:
  39. self.parent.handle_error((link, self), *sys.exc_info())

从代码中可以看出,join会保存当前greenlet.switch到一个队列中,并注册_notify_links回调,然后切换到hub,在_notify_links回调中将依次调用先前注册在队列中的回调。

而我们调用g1.join()将会把最外层greenlet.switch注册到队列中,当回调时就顺利结束程序了。很完美!!

转:[gevent源码分析] 深度分析gevent运行流程的更多相关文章

  1. python 协程库gevent学习--gevent源码学习(二)

    在进行gevent源码学习一分析之后,我还对两个比较核心的问题抱有疑问: 1. gevent.Greenlet.join()以及他的list版本joinall()的原理和使用. 2. 关于在使用mon ...

  2. Flink 源码解析 —— Standalone Session Cluster 启动流程深度分析之 Job Manager 启动

    Job Manager 启动 https://t.zsxq.com/AurR3rN 博客 1.Flink 从0到1学习 -- Apache Flink 介绍 2.Flink 从0到1学习 -- Mac ...

  3. Flink 源码解析 —— Standalone Session Cluster 启动流程深度分析之 Task Manager 启动

    Task Manager 启动 https://t.zsxq.com/qjEUFau 博客 1.Flink 从0到1学习 -- Apache Flink 介绍 2.Flink 从0到1学习 -- Ma ...

  4. 从源码的角度分析ViewGruop的事件分发

    从源码的角度分析ViewGruop的事件分发. 首先我们来探讨一下,什么是ViewGroup?它和普通的View有什么区别? 顾名思义,ViewGroup就是一组View的集合,它包含很多的子View ...

  5. 安卓图表引擎AChartEngine(二) - 示例源码概述和分析

    首先看一下示例中类之间的关系: 1. ChartDemo这个类是整个应用程序的入口,运行之后的效果显示一个list. 2. IDemoChart接口,这个接口定义了三个方法, getName()返回值 ...

  6. 从Android源码的角度分析Binder机制

    欢迎访问我的个人博客,原文链接:http://wensibo.top/2017/07/03/Binder/ ,未经允许不得转载! 前言 大家好,好久不见,距离上篇文章已经有35天之久了,因为身体不舒服 ...

  7. java基础解析系列(十)---ArrayList和LinkedList源码及使用分析

    java基础解析系列(十)---ArrayList和LinkedList源码及使用分析 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder jav ...

  8. 第九节:从源码的角度分析MVC中的一些特性及其用法

    一. 前世今生 乍眼一看,该标题写的有点煽情,最近也是在不断反思,怎么能把博客写好,让人能读下去,通俗易懂,深入浅出. 接下来几个章节都是围绕框架本身提供特性展开,有MVC程序集提供的,也有其它程序集 ...

  9. 通过官方API结合源码,如何分析程序流程

    通过官方API结合源码,如何分析程序流程通过官方API找到我们关注的API的某个方法,然后把整个流程执行起来,然后在idea中,把我们关注的方法打上断点,然后通过Step Out,从内向外一层一层分析 ...

随机推荐

  1. 关于document.write

    document.write的用处 document.write是JavaScript中对document.open所开启的文档流(document stream操作的API方法,它能够直接在文档流中 ...

  2. CS 和 BS 的区别和优缺点

    通俗来讲: bs是浏览器(browser)和服务器(server) cs是静态客户端程序(client)和服务器(server) 区别在于,虽然同样是通过一个程序连接到服务器进行网络通讯,但是bs结构 ...

  3. 支持IE6的树形节结构TreeTable

    关于TreeTable实际应用的案例:http://www.cnblogs.com/qigege/p/5213689.html treeTable是跨浏览器.性能很高的jquery的树表组件,它使用非 ...

  4. MotionEvent中getX()和getRawX()的区别

    http://blog.csdn.net/ztp800201/article/details/17218067 public class Res extends Activity implements ...

  5. 加载页面遮挡耗时操作任务页面--第三方开源--AndroidProgressLayout

    在Android的开发中,往往有这种需求,比如一个耗时的操作,联网获取网络图片.内容,数据库耗时读写等等,在此耗时操作过程中,开发者也许不希望用户再进行其他操作(其他操作可能会引起逻辑混乱),而此时需 ...

  6. linux下gcc编译的参数详细说明

    参考网址:1 http://hi.baidu.com/zengzhaonong/item/f1f9383565fa5c302e0f8125 gcc使用方法 汇总 2 http://s99f.blog. ...

  7. Win7任务计划自由预设系统定时自动关机

    大家在使用电脑的时候可能会遇到一些需要无人值守让电脑自行执行任务后定时关机的情形,在Win7系统中,我们可以使用"任务计划"设置功能结合shutdown命令灵活设置任务计划,让Wi ...

  8. Android学习笔记:TabHost 和 FragmentTabHost(转)

    转自:http://www.cnblogs.com/asion/p/3339313.html   作者:Asion Tang   出处:http://asiontang.cnblogs.com   T ...

  9. 常见的装置与其在Linux当中的档名

    需要特别留意的是硬盘机(不论是IDE/SCSI/U盘都一样),每个磁碟机的磁盘分区(partition)不同时, 其磁碟档名还会改变呢!下一小节我们会介绍磁盘分区的相关概念啦!需要特别注意的是磁带机的 ...

  10. javascript实现播放音乐

    <script language="javascript"> var flag = 0; //控制变量放在函数内起不到作用.function openplay() { ...