[gevent源代码分析] 深度分析gevent执行流程
一直对gevent执行流程比較模糊,近期看源代码略有所得。不敢独享。故分享之。
gevent是一个高性能网络库,底层是libevent,1.0版本号之后是libev。核心是greenlet。gevent和eventlet是亲近,唯一不同的是eventlet是自己实现的事件驱动。而gevent是使用libev。
两者都有广泛的应用,如openstack底层网络通信使用eventlet。goagent是使用gevent。
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveXVlZ3VhbmdoYWlkYW8=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="" />
或许大家会好奇,为什么採用这样的模式,为什么每次都要切换到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执行流程的更多相关文章
- 转:[gevent源码分析] 深度分析gevent运行流程
[gevent源码分析] 深度分析gevent运行流程 http://blog.csdn.net/yueguanghaidao/article/details/24281751 一直对gevent运行 ...
- angularjs源码分析之:angularjs执行流程
angularjs用了快一个月了,最难的不是代码本身,而是学会怎么用angular的思路思考问题.其中涉及到很多概念,比如:directive,controller,service,compile,l ...
- debian下使用dynamic printk分析usb转串口驱动执行流程
看了一篇文章<debug by printing>,文中提到了多种通过printk来调试驱动的方法,其中最有用的就是"Dynamic debugging". “Dyna ...
- 从 mian 函数开始一步一步分析 nginx 执行流程(二)
如不做特殊说明,本博客所使用的 nginx 源码版本是 1.0.14,[] 中是代码所在的文件! 上一个博客中我们将 main 函数执行流程分析完,到最后一步调用 ngx_master_process ...
- angularjs执行流程
angularjs源码分析之:angularjs执行流程 angularjs用了快一个月了,最难的不是代码本身,而是学会怎么用angular的思路思考问题.其中涉及到很多概念,比如:directi ...
- 【高并发】通过源码深度分析线程池中Worker线程的执行流程
大家好,我是冰河~~ 在<高并发之--通过ThreadPoolExecutor类的源码深度解析线程池执行任务的核心流程>一文中我们深度分析了线程池执行任务的核心流程,在ThreadPool ...
- Monkey源代码分析之执行流程
在<MonkeyRunner源代码分析之与Android设备通讯方式>中.我们谈及到MonkeyRunner控制目标android设备有多种方法.当中之中的一个就是在目标机器启动一个mon ...
- spark-sql执行流程分析
spark-sql 架构 图1 图1是sparksql的执行架构,主要包括逻辑计划和物理计划几个阶段,下面对流程详细分析. sql执行流程 总体流程 parser:基于antlr框架对 sql解析,生 ...
- Flink 源码解析 —— Standalone Session Cluster 启动流程深度分析之 Job Manager 启动
Job Manager 启动 https://t.zsxq.com/AurR3rN 博客 1.Flink 从0到1学习 -- Apache Flink 介绍 2.Flink 从0到1学习 -- Mac ...
随机推荐
- 【PAT】1008. 数组元素循环右移问题 (20)
1008. 数组元素循环右移问题 (20) 一个数组A中存有N(N>0)个整数,在不允许使用另外数组的前提下,将每个整数循环向右移M(M>=0)个位置,即将A中的数据由(A0A1……AN- ...
- ASP.NET WebAPI 05 参数绑定
ParameterBindingAttribute 在上一篇中重点讲了ModelBinderAttribute的使用场景.这一篇详细的讲一下ModelBinder背后的参数绑定原理. ModelBin ...
- [实战]MVC5+EF6+MySql企业网盘实战(5)——页面模板
写在前面 上篇文章更新了网盘的登录界面,以及用户头像的等比例压缩功能.今天折腾一下页面的样式,将从网上找的一个模板套用在项目中. 系列文章 [EF]vs15+ef6+mysql code first方 ...
- js中post中文参数转码和解码
作为基础知识和血淋林的教训,前端一定要记得post请求时将参数中带有中文的部分进行转码!! var str='宋宇·特弱夫斯基'; //转码: encodeURI(encodeURI(str)) ; ...
- LeetCode 628. 三个数的最大乘积
题目描述 LeetCode 628. 三个数的最大乘积 给定一个整型数组,在数组中找出由三个数组成的最大乘积,并输出这个乘积. 示例1 输入: [1,2,3] 输出: 6 示例2 输入: [1,2,3 ...
- java短信接口调用
java短信接口调用 之前一直在一个传统的单位上班好多听容易的技术都没接触过,即使有时候想搞一搞类似于支付宝支付,短信接口调用,微信公众号,小程序之类等功能,一直有心无力终于跳槽了,估计是氛围的原因吧 ...
- Tomcat在Eclips中的使用及注意细节
1.运行环境,先配置Eclips Eclips中的Windows→ preferences→弹出框左边Server→Runtime Environments→右边Add添加需要的Apach Tomca ...
- Vue 2.0学习(二)数据绑定
Vue实例对象 创建一个vue应用很简单,通过构造函数Vue就能创建一个Vue的根实例: var app = new Vue({ el: '#app', data: { message: 'Hello ...
- WordPress插件会员简化1.58 -任意文件下载漏洞(附poc)
今天我们将讨论在WordPress插件WordPress插件与重点会员简化v1.58作为这个剧本的创作时间不打补丁的扶贫开发实践.脆弱脚本如下: CVE-ID:cve-2017-1002008 当然, ...
- Velocity模板学习(一)
一.Velocity是什么 Velocity是一个基于Java的模板引擎,允许任何人仅仅简单地使用模板语言就可以引用由Java代码编写的对象. 二.Velocity的基本语法 1.变量 变量的定义 在 ...