转:[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。
也许大家会好奇,为什么采用这种模式,为什么每次都要切换到hub?我想理由有二:
1.hub是事件驱动的核心,每次切换到hub后将继续循环事件。如果在一个greenlet中不出来,那么其它greenlet将得不到调用。
2.维持两者关系肯定比维持多个关系简单。每次我们所关心的就是hub以及当前greenlet,不需要考虑各个greenlet之间关系。
我们看看最简单的gevent.sleep发生了什么?
我们先想想最简单的sleep(0)该如何调度?根据上面很明显
1.向事件循环注册当前greenlet的switch函数
2.切换到hub,运行主事件循环
- def sleep(seconds=0, ref=True):
- hub = get_hub()
- loop = hub.loop
- if seconds <= 0:
- waiter = Waiter()
- loop.run_callback(waiter.switch)
- waiter.get()
- else:
- 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是什么。
- >>> result = Waiter()
- >>> timer = get_hub().loop.timer(0.1)
- >>> timer.start(result.switch, 'hello from Waiter')
- >>> result.get() # blocks for 0.1 seconds
- 'hello from Waiter'
timer.start(result.switch, 'hello from Waiter')我们向hub的主循环注册一个0.1s的定时器,回调为result.switch,然后将执行result.get(),此时过程代码如下:
- def get(self):
- assert self.greenlet is None, 'This Waiter is already used by %r' % (self.greenlet, )
- self.greenlet = getcurrent()
- try:
- return self.hub.switch()
- finally:
- self.greenlet = None
将把self.greenlet设置为当前greenlet,然后通过self.hub.switch()切换到主循环,很明显在主循环中将回调result.switch,看代码:
- def switch(self, value=None):
- """Switch to the greenlet if one's available. Otherwise store the value."""
- greenlet = self.greenlet
- assert getcurrent() is self.hub, "Can only use Waiter.switch method from the Hub greenlet"
- switch = greenlet.switch
- try:
- switch(value)
- except:
- self.hub.handle_error(switch, *sys.exc_info())
拿到刚保存的greenlet,然后切换到greenlet.switch(),返回到我们刚调用reuslt.get()方法。通过上面assert我们也可以看出这是在hub中调用的。
通过以上分析,小伙伴们肯定都懂了gevent的执行流程了。
这里有个问题,如果上面先发生result.switch,那又该如何呢?就像下面这样:
- >>> result = Waiter()
- >>> timer = get_hub().loop.timer(0.1)
- >>> timer.start(result.switch, 'hi from Waiter')
- >>> sleep(0.2)
- >>> result.get() # returns immediatelly without blocking
- 'hi from Waiter'
我想聪明的你,打开hub.py再看看源码肯定就明白了(上面Waiter代码是我特意简化的)。
既然我们知道了gevent运行流程,下面我们看看gevent.spawn和join到底做了什么?
gevent.spawn其实就是Greenlet.spawn,所以gevent.spawn就是创建一个greenlet,并将该greenlet的switch()加入hub主循环回调。
- class Greenlet(greenlet):
- """A light-weight cooperatively-scheduled execution unit."""
- def __init__(self, run=None, *args, **kwargs):
- hub = get_hub()
- greenlet.__init__(self, parent=hub)
- if run is not None:
- self._run = run
- self._start_event = None
- def start(self):
- """Schedule the greenlet to run in this loop iteration"""
- if self._start_event is None:
- self._start_event = self.parent.loop.run_callback(self.switch)
- @classmethod
- def spawn(cls, *args, **kwargs):
- """Return a new :class:`Greenlet` object, scheduled to start.
- The arguments are passed to :meth:`Greenlet.__init__`.
- """
- g = cls(*args, **kwargs)
- g.start()
- return g
通过下面代码证明:
- import gevent
- def talk(msg):
- print(msg)
- g1 = gevent.spawn(talk, 'bar')
- gevent.sleep(0)
将输出:bar,我们通过sleep切换到hub,然后hub将运行我们添加的回调talk,一切正常。
此时不要沾沾自喜,如果下面代码也觉得一切正常再高兴也不迟。
- import gevent
- def talk(msg):
- print(msg)
- gevent.sleep(0)
- print msg
- g1 = gevent.spawn(talk, 'bar')
- 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不就可以了吗?这将导致主循环结束不了的问题。
- import gevent
- def talk(msg):
- print(msg)
- gevent.sleep(0)
- print msg
- g1 = gevent.spawn(talk, 'bar')
- gevent.get_hub().switch()
程序输出:
- bar
- bar
- Traceback (most recent call last):
- File "F:\py_cgi\geve.py", line 9, in <module>
- gevent.get_hub().switch()
- File "C:\Python26\lib\site-packages\gevent\hub.py", line 331, in switch
- return greenlet.switch(self)
- gevent.hub.LoopExit: This operation would block forever
虽然成功的输出了两次“bar",但也导致了更为严重的问题。
这也就是join存在的价值,我们看看join是如何做到的?
- def join(self, timeout=None):
- """Wait until the greenlet finishes or *timeout* expires.
- Return ``None`` regardless.
- """
- if self.ready():
- return
- else:
- switch = getcurrent().switch
- self.rawlink(switch)
- try:
- t = Timeout.start_new(timeout)
- try:
- result = self.parent.switch()
- assert result is self, 'Invalid switch into Greenlet.join(): %r' % (result, )
- finally:
- t.cancel()
- except Timeout:
- self.unlink(switch)
- if sys.exc_info()[1] is not t:
- raise
- except:
- self.unlink(switch)
- raise
- def rawlink(self, callback):
- """Register a callable to be executed when the greenlet finishes the execution.
- WARNING: the callable will be called in the HUB greenlet.
- """
- if not callable(callback):
- raise TypeError('Expected callable: %r' % (callback, ))
- self._links.append(callback)
- if self.ready() and self._links and not self._notifier:
- self._notifier = self.parent.loop.run_callback(self._notify_links)
- def _notify_links(self):
- while self._links:
- link = self._links.popleft()
- try:
- link(self)
- except:
- self.parent.handle_error((link, self), *sys.exc_info())
从代码中可以看出,join会保存当前greenlet.switch到一个队列中,并注册_notify_links回调,然后切换到hub,在_notify_links回调中将依次调用先前注册在队列中的回调。
而我们调用g1.join()将会把最外层greenlet.switch注册到队列中,当回调时就顺利结束程序了。很完美!!
转:[gevent源码分析] 深度分析gevent运行流程的更多相关文章
- python 协程库gevent学习--gevent源码学习(二)
在进行gevent源码学习一分析之后,我还对两个比较核心的问题抱有疑问: 1. gevent.Greenlet.join()以及他的list版本joinall()的原理和使用. 2. 关于在使用mon ...
- Flink 源码解析 —— Standalone Session Cluster 启动流程深度分析之 Job Manager 启动
Job Manager 启动 https://t.zsxq.com/AurR3rN 博客 1.Flink 从0到1学习 -- Apache Flink 介绍 2.Flink 从0到1学习 -- Mac ...
- Flink 源码解析 —— Standalone Session Cluster 启动流程深度分析之 Task Manager 启动
Task Manager 启动 https://t.zsxq.com/qjEUFau 博客 1.Flink 从0到1学习 -- Apache Flink 介绍 2.Flink 从0到1学习 -- Ma ...
- 从源码的角度分析ViewGruop的事件分发
从源码的角度分析ViewGruop的事件分发. 首先我们来探讨一下,什么是ViewGroup?它和普通的View有什么区别? 顾名思义,ViewGroup就是一组View的集合,它包含很多的子View ...
- 安卓图表引擎AChartEngine(二) - 示例源码概述和分析
首先看一下示例中类之间的关系: 1. ChartDemo这个类是整个应用程序的入口,运行之后的效果显示一个list. 2. IDemoChart接口,这个接口定义了三个方法, getName()返回值 ...
- 从Android源码的角度分析Binder机制
欢迎访问我的个人博客,原文链接:http://wensibo.top/2017/07/03/Binder/ ,未经允许不得转载! 前言 大家好,好久不见,距离上篇文章已经有35天之久了,因为身体不舒服 ...
- java基础解析系列(十)---ArrayList和LinkedList源码及使用分析
java基础解析系列(十)---ArrayList和LinkedList源码及使用分析 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder jav ...
- 第九节:从源码的角度分析MVC中的一些特性及其用法
一. 前世今生 乍眼一看,该标题写的有点煽情,最近也是在不断反思,怎么能把博客写好,让人能读下去,通俗易懂,深入浅出. 接下来几个章节都是围绕框架本身提供特性展开,有MVC程序集提供的,也有其它程序集 ...
- 通过官方API结合源码,如何分析程序流程
通过官方API结合源码,如何分析程序流程通过官方API找到我们关注的API的某个方法,然后把整个流程执行起来,然后在idea中,把我们关注的方法打上断点,然后通过Step Out,从内向外一层一层分析 ...
随机推荐
- [javascript|基本概念|一元操作符]学习笔记
只操作一个值的操作符 递增/递减操作符 前置型/后置型 前置型:操作符位于操作数前面 e.g.: var a = 30; ++a; 等同于 var a = 30; a = a + 1; --> ...
- ios固定高度禁止惯性滚动
最近测试pad改H5的项目时,固定高度的div,超出部分滚动,但在ios下滑动特别卡顿,安卓上没问题.搜索找到解决办法 固定高度的div设置超出页面滚动,ios会出现卡顿,非常不爽.通过下面css就可 ...
- NDK 通过java调用so文件
首先我们来看so文件的来源 1. 自己写.c文件,然后生成so库 2. 引用别人的静态库,或者动态库来生成新的jni调用库. 我们先来看最简单的编写一个jni调用的so库,包含一个获取字符串的方法,通 ...
- <邮件服务postfix+mysql>MAIL第二篇
环境:本服务是建立在第一篇的基础之上的,最好搭建好第一篇 玩此服务的前提是你的系统装好了msql和postfix服务. Postfix+mysql主要是把邮件服务的发与mysql结合使用.当然mysq ...
- JS对select动态添加options操作[IE&FireFox兼容]
<select id="ddlResourceType" onchange="getvalue(this)"> </select> 动态 ...
- Resource temporarily unavailable
数据库版本:5.5.14 操作系统版本:contos 6.3 服务器256G内存,安装90个实例.通过脚本启动90个mysql数据库实例,会有几个实例无法启动,进程启动后直接被杀死.查看mysql日志 ...
- Teradata 的rank() 和 row_number() 函数
Teradata数据库中也有和oracle类似的分析函数,功能基本一样.示例如下: RANK() 函数 SELECT * FROM salestbl ORDER BY 1,2; storeid p ...
- c++各种数据类型表示范围
符号属性 长度属性 基本型 所占位数 取值范围 输入符举例 输出符举例 -- -- char ...
- 【HTML5】websocket 初识
什么是WebSocket API? WebSocket API是下一代客户端-服务器的异步通信方法.该通信取代了单个的TCP套接字,使用ws或wss协议,可用于任意的客户端和服务器程序.WebSock ...
- java 泛型通配符 extends, super
引自:http://sharewind.iteye.com/blog/1622164 关键字说明 ? 通配符类型 <? extends T> 表示类型的上界,表示参数化类型的可能是T 或是 ...