python 协程库gevent学习--gevent源码学习(二)
在进行gevent源码学习一分析之后,我还对两个比较核心的问题抱有疑问:
1. gevent.Greenlet.join()以及他的list版本joinall()的原理和使用。
2. 关于在使用monkey_patchall()之后隐式切换的问题。
下面我将继续通过分析源码及其行为来加以理解和掌握。
1. 关于gevent.Greenlet.join()(以下简称join)先来看一个例子:
import gevent def xixihaha(msg):
print(msg)
gevent.sleep(0)
print msg g1 = gevent.spawn(xixihaha, 'xixi')
gevent.sleep(0)
先分析一波
1. 初始化一个Greenlet实例g1,将该Greenlet.switch注册到hub上。
2. 然后调用gevent.sleep(0)将当前greenlet保存下来放在Waiter()中并向hub注册该回调。这里再贴一次实现的代码跟着走一遍强调一下实现,这对一会儿理解join实现非常有帮助:
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))
这里我们将second设置为0,所以走第一层判断,初始化一个Waiter()对象给waiter,随后注册waiter.switch方法到hub,调用waiter.get()去调用hub的switch()方法(注意这里的self.hub.switch()方法并没有切换这个概念。这只是Hub类中自己实现的一个switch方法而已):
def get(self):
"""If a value/an exception is stored, return/raise it. Otherwise until switch() or throw() is called."""
if self._exception is not _NONE:
if self._exception is None:
return self.value
else:
getcurrent().throw(*self._exception)
else:
if self.greenlet is not None:
raise ConcurrentObjectUseError('This Waiter is already used by %r' % (self.greenlet, ))
self.greenlet = getcurrent()
try:
return self.hub.switch()
finally:
self.greenlet = None
然后hub.switch(self)会返回一个greenlet.switch(self)这里才是切换 然后他会调用自己的run方法(greenlet底层实现)。
def switch(self):
switch_out = getattr(getcurrent(), 'switch_out', None)
if switch_out is not None:
switch_out()
return greenlet.switch(self)
def run(self):
"""
Entry-point to running the loop. This method is called automatically
when the hub greenlet is scheduled; do not call it directly. :raises LoopExit: If the loop finishes running. This means
that there are no other scheduled greenlets, and no active
watchers or servers. In some situations, this indicates a
programming error.
"""
assert self is getcurrent(), 'Do not call Hub.run() directly'
while True:
loop = self.loop
loop.error_handler = self
try:
loop.run()
finally:
loop.error_handler = None # break the refcount cycle
self.parent.throw(LoopExit('This operation would block forever', self))
# this function must never return, as it will cause switch() in the parent greenlet
# to return an unexpected value
# It is still possible to kill this greenlet with throw. However, in that case
# switching to it is no longer safe, as switch will return immediatelly
这里开始就进入事件loop循环了,run()会调用到注册过来的回调。这里开始g1注册过来的回调就会被调用了。
之后的流程就是运行g1注册的回调,然后运行Waiter()注册的回调,然后回到外层最后结束掉。打印结果:
xixi
那还有一个msg没有打印呢!怎么就退出来了!!这不科学。所以这就是join可以办到的事情了,来看源码:
def join(self, timeout=None):
"""Wait until the greenlet finishes or *timeout* expires.
Return ``None`` regardless.
"""
if self.ready():
return switch = getcurrent().switch
self.rawlink(switch)
try:
t = Timeout._start_new_or_dummy(timeout)
try:
result = self.parent.switch()
if result is not self:
raise InvalidSwitchError('Invalid switch into Greenlet.join(): %r' % (result, ))
finally:
t.cancel()
except Timeout as ex:
self.unlink(switch)
if ex is not t:
raise
except:
self.unlink(switch)
raise
将当前的greenlet.switch方法赋值给switch然后调用rawlink方法:
def rawlink(self, callback):
"""Register a callable to be executed when the greenlet finishes execution. The *callback* will be called with this instance as an argument. .. caution:: 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)
rawlink其实也没做什么,他将当前greenlet的switch存进了一个双端链表中,就是self._links.append(callback)这一句。保存了起来并没有像sleep那样像hub上注册回调,所以hub在回调链里是没有这家伙的。
然后还是跟上面一样的流程用self.parent.switch()回到hub中调用greenlet.switch(self)运行run函数进入loop循环。然后运行第一个注册进来的回调也就是运行xixihaha并打印第一个msg。这个时候调用gevent.sleep注册一个Waiter()事件到hub,然后依然会回来。然后再执行最后一个msg 因为整个回调链上只有他自己只能又切回来。当运行完之后我们来看下如何回到最外面main:
def run(self):
try:
self.__cancel_start()
self._start_event = _start_completed_event try:
result = self._run(*self.args, **self.kwargs)
except:
self._report_error(sys.exc_info())
return
self._report_result(result)
finally:
self.__dict__.pop('_run', None)
self.__dict__.pop('args', None)
self.__dict__.pop('kwargs', None)
当xixihaha回调也结束之后也就是第二个msg也运行完了之后会返回调用他的那个Greenlet.run方法继续向下执行,然后会执行到self._report_result(result)
def _report_result(self, result):
self._exc_info = (None, None, None)
self.value = result
if self._has_links() and not self._notifier:
self._notifier = self.parent.loop.run_callback(self._notify_links)
这里我们直接看判断这里,会去hub上注册self._notify_links。 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())
self._links.popleft()会让你把前面赋值给self._links的回调吐出来赋值给link然后运行这个回调。结果当然是愉快的回到了main里面。
然后弹出乱七八糟的东西最后结束run。
总结:
可以看到join和joinall()类似的都是没有把自己注册到hub主循环之中,而是等所有的greenlet都运行完了之后,再调用自己回到原来注册过去的greenlet中,就不会在因为提前切到主函数main中导致整个过程提前结束。
2. 关于在使用monkey_patchall()之后隐式切换的问题:
这个我不准备再拿特别大篇幅来分析讲解了,大致说下我的理解,首先必须要知道一点就是gevent的底层是libev,这也是为什么他如此高效的原因。hub里面的loop下就封装了各种各样对应的libev事件。就拿gevent.sleep()来举例子,里面使用的self.parent.loop.timer就是注册timer事件,而网络请求的切换只是将时间到期事件变成了io读写事件。每当io读取事件,写事件发生的时候,就会触发对应的事件gevent就是通过这些事件的触发来决定自身什么时候该切换到哪里进行哪些事件的处理。理解了这个,也就明白了隐式切换真正的实现原理。然后再去看源码可能就没有那么一脸萌比的感觉了。
对gevent的源码分析到这里,目前也足够我使用了。下一篇关于gevent的文章将分析和实践一些高级应用和特性,毕竟我们学库都是拿来使用的。
python 协程库gevent学习--gevent源码学习(二)的更多相关文章
- python协程详解,gevent asyncio
python协程详解,gevent asyncio 新建模板小书匠 #协程的概念 #模块操作协程 # gevent 扩展模块 # asyncio 内置模块 # 基础的语法 1.生成器实现切换 [1] ...
- 手牵手,从零学习Vue源码 系列二(变化侦测篇)
系列文章: 手牵手,从零学习Vue源码 系列一(前言-目录篇) 手牵手,从零学习Vue源码 系列二(变化侦测篇) 陆续更新中... 预计八月中旬更新完毕. 1 概述 Vue最大的特点之一就是数据驱动视 ...
- python 协程库gevent学习--源码学习(一)
总算还是要来梳理一下这几天深入研究之后学习到的东西了. 这几天一直在看以前跟jd对接的项目写的那个gevent代码.为了查错,基本上深入浅出了一次gevent几个重要部件的实现和其工作的原理. 这里用 ...
- python 协程库gevent学习--gevent数据结构及实战(三)
gevent学习系列第三章,前面两章分析了大量常用几个函数的源码以及实现原理.这一章重点偏向实战了,按照官方给出的gevent学习指南,我将依次分析官方给出的7个数据结构.以及给出几个相应使用他们的例 ...
- python 协程库gevent学习--gevent数据结构及实战(四)
一不留神已经到第四部分了,这一部分继续总结数据结构和常用的gevent类,废话不多说继续. 1.Timeout错误类 晚上在调试调用第三方接口的时候,发现有些接口耗时非常多,觉得应该有个超时接口来限制 ...
- 菜鸟学习Fabric源码学习 — 背书节点和链码容器交互
Fabric 1.4 源码分析 背书节点和链码容器交互 本文档主要介绍背书节点和链码容器交互流程,在Endorser背书节点章节中,无论是deploy.upgrade或者调用链码,最后都会调用Chai ...
- 菜鸟学习Fabric源码学习 — kafka共识机制
Fabric 1.4源码分析 kafka共识机制 本文档主要介绍kafka共识机制流程.在查看文档之前可以先阅览raft共识流程以及orderer服务启动流程. 1. kafka 简介 Kafka是最 ...
- 学习JDK源码(二):Integer
最近没有好好保持学习的好习惯,该打. 天天忙,感觉都不知道在干嘛.真的厌倦了普通的Java代码,还是想学点新技术. 用了这么久的Java,最常用的数据类型肯定是Int了,而他的包装类Integer用的 ...
- 菜鸟学习Fabric源码学习 — Endorser背书节点
Fabric 1.4 源码分析 Endorser背书节点 本文档主要介绍fabric背书节点的主要功能及其实现. 1. 简介 Endorser节点是peer节点所扮演的一种角色,在peer启动时会创建 ...
随机推荐
- Linux系统中将普通用户添加到sudoers
我们经常会在普通用户下用到sudo命令,为了不用频繁切换root用户,我们可以将普通用户添加到sudoers里面.Linux默认是没有将用户添加到sudoers列表中的,需要root手动将账户添加到s ...
- matlab slice
前言:在地球物理勘探,流体空间分布等多种场景中,定位空间点P(x,y,x)的物理属性值Q,并绘制三维空间分布图,对我们洞察空间场景有十分重要的意义. 1. 三维立体图的基本要件: 全空间网格化 网格节 ...
- vue指令相关的
阅读目录 1.v-text 2.v-html 3.v-show 4.v-if 5.v-if vs v-show 6.v-else 7.v-for 8.v-on 9.v-bind 和 v-model 1 ...
- 浅谈Java泛型中的extends和super关键字
泛型是在Java 1.5中被加入了,这里不讨论泛型的细节问题,这个在Thinking in Java第四版中讲的非常清楚,这里要讲的是super和extends关键字,以及在使用这两个关键字的时候为什 ...
- WPF中TreeView.BringIntoView方法的替代方案
原文:WPF中TreeView.BringIntoView方法的替代方案 WPF中TreeView.BringIntoView方法的替代方案 周银辉 WPF中TreeView.BringIntoVie ...
- TopShelf 自动配置Service测试
在开发中经常会遇到后台定时处理数据和任务的情况,处理这些事情大概有以下几种方案: 1.使用数据库的job功能.优点是在数据库中可以完成的就在数据库中完成,配置等基础设施数据库都提供,简单快捷.缺点是如 ...
- Nginx URL后面不加斜杠301重定向
今天开发碰到一个问题,其实之前就有这个问题,但是一直都没去关注,今天测试碰到了就解决一下. 问题情况: 当我请求 http://admindev.jingruiauto.com/store/views ...
- MongoDB的导入导出
一:MongoDB的导出功能 mongodb提供了导入和导出的功能,分别是MongoDB下载目录下的mongoexport.exe和mongoimport.exe文件 ,具体的导出命令格式如下: mo ...
- cython学习
学习网址:http://blog.csdn.net/i2cbus/article/details/23791309
- 机器学习 第四篇:OLS回归分析
变量之间存在着相关关系,比如,人的身高和体重之间存在着关系,一般来说,人高一些,体重要重一些,身高和体重之间存在的是不确定性的相关关系.回归分析是研究相关关系的一种数学工具,它能帮助我们从一个变量的取 ...