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启动时会创建 ...
随机推荐
- go标准库的学习-crypto/sha1
参考:https://studygolang.com/pkgdoc 导入方式: import "crypto/sha1" sha1包实现了SHA1哈希算法,参见RFC 3174. ...
- 树莓派开启SSH
2017-04-10-raspbian-jessie默认SSH功能时关闭的 boot下新建一个ssh的空文件
- centos7环境搭建
1. tar命令安装 yum install -y tar 2. jdk8下载 wget --no-check-certificate --no-cookies --header "Cook ...
- Centos7下完美安装并配置mysql5.6
Centos7将默认数据库mysql替换成了Mariadb,对于我们这些还想用mysql的人来说并不是一个好消息. 最近我搜罗了网上各种安装教程,各种出问题,要么安装失败,要么安装成功了却使用不了my ...
- docker命名空间、控制组及联合文件系统概念
基本架构 命名空间 控制组 联合文件系统 docker底层依赖的核心技术主要包括操作系统的命名空间(Namespace).控制组(Control Groups).联合文件系统(Union File S ...
- ArrayList源码中的两个值得注意的问题
1.“拖泥带水”的删除 测试代码: package com.demo; import java.util.ArrayList; public class TestArrayList { public ...
- linux中断源码分析 - 概述(一)
本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 关于中断和异常 一般在书中都会把中断和异常一起说明,因为它们具有相同的特点,同时也有不同的地方.在CPU里,中断 ...
- 数据库sql的in操作,解决in的过多
一个sql的拼写后,服务器会把这个sql传送到数据库服务器执行,一般不在一个物理机上.那么传送需要走网络,包丢失等网络情况就可能出现. 一般情况,一个sql的长度不会很大,但是有种这样的情况.in操作 ...
- RocketMQ环境搭建
1 源码下载 wget http://mirror.bit.edu.cn/apache/rocketmq/4.2.0/rocketmq-all-4.2.0-bin-release.zip unzip ...
- ORA-00020:maximum number of processes (150) exceeded
异常的含义 超过最大的进程数 我们使用下面的语句可以查看与进程(process)的相关参数: 如上所示,这里的最大进程数是150. 问题可能存在的原因 1.应用程序在使用数据库连接池时,使用完成后没有 ...