python 协程库gevent学习--源码学习(一)
总算还是要来梳理一下这几天深入研究之后学习到的东西了。
这几天一直在看以前跟jd对接的项目写的那个gevent代码。为了查错,基本上深入浅出了一次gevent几个重要部件的实现和其工作的原理。
这里用一个简单demo依次分析运行流程和介绍相关概念最后得出结论:
import gevent def test_1():
print '切换不出去'
print '切换出去我不是循环'
gevent.sleep(1) def test_2():
print '切换'
print '切换出去我去'
gevent.sleep(3) gevent.spawn(test_1)
gevent.spawn(test_2)
gevent.sleep(1)
在具体介绍各部分具体怎么运转得时候我想要先提几个一定会用到的gevent类:
gevent hub:可以在图上明显看到,hub.loop其实就是gevent的事件循环核心。这也是所有Greenlet实例的parents。想要实现回调,需要去hub上注册一个你当前栈的回调,以让hub在处理完其他事情之后能使用greenlet.switch(注意大写的Greenlet是gevent重新实现的类继承了greenlet。区别文中的大小写对于理解很重要)回到原来的栈中。
Waiter类:其实我觉得Waiter类要理解到他的功能之后,才会觉得比较简单。我们可以把Waiter实例化之后将他的switch方法注册到hub中。这里看一段代码:
result = Waiter()
timer = get_hub().loop.timer(5)
timer.start(result.switch, 'hello from Waiter')
print result.get()
也就是上面的第三行。这里的第二行可以实现向主循环中注册一个5秒等待事件。注册之后就开始计时了。最后调用get方法去切换到hub主循环。下面上get的代码:
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:
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 = getcurrent(), 将当前栈的保存到self.greenlet中,以保证后面可以通过开始向主循环中注册的Waiter().switch切换回来。然后调用self.hub.switch()方法:
def switch(self):
switch_out = getattr(getcurrent(), 'switch_out', None)
if switch_out is not None:
switch_out()
return greenlet.switch(self)
这里最后一句像主循环进行切换之后,运行hub的run方法:
def run(self):
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'))
# 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方法,就可以开始依次运行回调了。在第一次执行的时候到这里启用hub的loop循环然后执行了loop.run之后就是依次运行注册的回调。
下次再有的回调运行的都是在loop循环里执行不会再运行到hub调用run方法了这里注意。
执行注册过来的Waiter().switch回调切换到Waiter.switch中进行执行继续看代码:
def switch(self, value=None):
"""Switch to the greenlet if one's available. Otherwise store the value."""
greenlet = self.greenlet
if greenlet is None:
self.value = value
self._exception = None
else:
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())
将当时Waiter()里面保存的greenlet拿出来,带上value参数切换回去。这里相当于我们切换回main。回到当时切换到hub的地方:
try:
return self.hub.switch()
finally:
self.greenlet = None
return self.hub.switch()带回来的值然后执行finally清空Waiter()的greenlet的值为None结束运行。下面我会用不同的例子来展示Waiter的用法。
greenlet: greenlet 提供了一种在不同的调用栈之间自由跳跃的功能。
libev: 这里用到了loop watcher.timer,其实真正在处理io事件的时候这个才是更重要的。
下面开始讲解最顶上贴出的例子:
贴出gevent.spawn的源码:
@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
运行到gevent.spawn(test1)的时候会实例化一个Greenlet实例g。g调用了start方法,将g.switch注册到hub中。以下是start方法的源码:
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)
调用loop.run_callback注册greenlet.switch方法到主循环hub中。
总的来说gevent.spawn()干的事情就是生成一个Greenlet实例,然后将这个实例的self.switch方法注册到主循环回调中。test2同理,直接看到gevent.sleep(1)。
gevent.sleep()是非常重要也非常有用的实现,我觉得这是理解gevent显式切换(explicit switch)的关键。我不想精简代码,所以贴上所有源码慢慢分析:
def sleep(seconds=0, ref=True):
"""Put the current greenlet to sleep for at least *seconds*. *seconds* may be specified as an integer, or a float if fractional seconds
are desired. If *ref* is false, the greenlet running sleep() will not prevent gevent.wait()
from exiting.
"""
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))
还是先获得hub,然后将hub.loop保存给loop,之后判断有没有传seconds参数我们传递了seconds参数为1s,于是调用hub的wait方法将watcher loop.timer()做参数传递进去。
这里watcher的叫法来源于libev事件驱动库,hub.loop中对底层的libev库做了一一对应的封装。这里我们使用的是一个timer的watcher,那么当我们处理io事件的时候,使用的事件驱动可能就会变成io的watcher了。继续往下看hub.wait函数:
def wait(self, watcher):
waiter = Waiter()
unique = object()
watcher.start(waiter.switch, unique)
try:
result = waiter.get()
assert result is unique, 'Invalid switch into %s: %r (expected %r)' % (getcurrent(), result, unique)
finally:
watcher.stop()
实例化Waiter()类。然后向loop.timer的watcher注册一个waiter.switch,带了个参数unique。然后执行waiter.get()
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:
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 = getcurrent(), 将当前栈的保存到self.greenlet中,以保证后面可以通过开始向主循环中注册的Waiter().switch切换回来,然后调用self.hub.switch()。
def switch(self):
switch_out = getattr(getcurrent(), 'switch_out', None)
if switch_out is not None:
switch_out()
return greenlet.switch(self)
然后到hub.switch()函数中切换携程到Hub中执行run()。
def run(self):
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'))
# 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。这里loop.run就会开始执行刚才注册上来的回调,我们第一个注册上来的回调是_run=test1的Greenlet回调。这里注意这里执行的run方法其实就已经是Greenlet的run方法了,回调回来的时候Greenlet继承的greenlet底层实现了执行的时候会执行他的run方法,我们也可以通过重写_run方法自己定义这里会执行的逻辑。
def run(self):
try:
if self._start_event is None:
self._start_event = _dummy_event
else:
self._start_event.stop()
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)
当执行到这一句 result = self._run(*self.args, **self.kwargs)的时候,我们会执行最开始保存在Greenlet中的_run函数也就是test1。然后就会去执行test1函数了。
执行test1函数之后我们继续使用gevent.sleep(1)向hub注册回调。注意这个回调肯定要排在main函数也就是最外层函数的后面,不管外面函数休眠多少秒这里都会等待,因为很重要的一点是hub本身其实是顺序且阻塞的。
然后这个时候会继续运行到test2所在的Greenlet对象执行之后继续用gevent.sleep(1)注册回调。然后下一次再运行到self.hub.switch时greenlet.switch(self)方法就会切换到第一个注册的Waiter().switch()上了,也就是我们在main上面使用gevent.sleep(1)注册的那个回调。这个回调会让我们顺利返回到main上的那个greenlet.至此就结束了整个过程。
其实切换比较难以理解的我觉得还是思路和那可恶的到处都是的switch。而且各类的switch方法还都叫switch。。。一不小心就弄错,绝对是难理解的关键。 我本人不用本子记的时候看得非常晕。这些东西我相信只有多看才能够理解。 后面的文章我会继续探索,隐式切换的方方面面 以及写一些实例来控制切换。gevent 这家伙给我埋坑太深,我已经下决心要完全摸透了。
Reference:
http://blog.csdn.net/yueguanghaidao/article/details/24281751 gevent源码分析
https://segmentfault.com/a/1190000000613814 gevent源码分析
http://xlambda.com/gevent-tutorial/#_2 gevent指南
http://blog.csdn.net/yueguanghaidao/article/details/39122867 [gevent源码分析] gevent两架马车-libev和greenlet
python 协程库gevent学习--源码学习(一)的更多相关文章
- python 协程库gevent学习--gevent源码学习(二)
在进行gevent源码学习一分析之后,我还对两个比较核心的问题抱有疑问: 1. gevent.Greenlet.join()以及他的list版本joinall()的原理和使用. 2. 关于在使用mon ...
- python 协程库gevent学习--gevent数据结构及实战(三)
gevent学习系列第三章,前面两章分析了大量常用几个函数的源码以及实现原理.这一章重点偏向实战了,按照官方给出的gevent学习指南,我将依次分析官方给出的7个数据结构.以及给出几个相应使用他们的例 ...
- python 协程库gevent学习--gevent数据结构及实战(四)
一不留神已经到第四部分了,这一部分继续总结数据结构和常用的gevent类,废话不多说继续. 1.Timeout错误类 晚上在调试调用第三方接口的时候,发现有些接口耗时非常多,觉得应该有个超时接口来限制 ...
- 协程,greenlet原生协程库, gevent库
协程简介 协程(coroutine),又称为微线程,纤程,是一种用户级的轻量级线程.协程拥有自己的寄存器上下文和栈. 协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来时,恢复之前保存的上下文 ...
- python协程初步--gevent库使用以及解释什么是猴子补丁monkey_patch
协程工作的特点是遇到阻塞或耗时的任务时就切换,协程的生存依赖于线程,线程依赖于进程 一个似乎有点问题的例子 import gevent,time def kisscpc(num): for i in ...
- python 协程 greenlet gevent
一.并发的本质 切换+保存状态 cpu正在运行一个任务,会在两种情况下切走去执行其他的任务(切换由操作系统强制控制),一种情况是该任务发生了阻塞,另外一种情况是该任务计算的时间过长时间片到了 二.协程 ...
- Python 协程(gevent)
协程,又叫微线程,协程是一种用户态的轻量级线程. 协程拥有自己的寄存器上下文和栈.协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈.因此: 协程能保留上 ...
- Dubbo学习-源码学习
Dubbo概述 dubbo框架提供多协议远程调用,服务提供方可以是分布式部署.dubbo框架可以很简单的帮我们实现微服务. 此处援引官网上图片 dubbo分为客户端和服务提供方 服务方将服务注册到注册 ...
- go标准库-log包源码学习
log包是go语言提供的一个简单的日志记录功能,其中定义了一个结构体类型 Logger,是整个包的基础部分,包中的其他方法都是围绕这整个结构体创建的. Logger结构 Logger结构的定义如下: ...
随机推荐
- WEB安全 - 认识与防御XSS攻击
目录 什么是xss攻击? XSS的危害 XSS攻击分类 xss攻击示例 反射型攻击 - 前端URL参数解析 反射型攻击 - 后端URL参数解析 注入型攻击 - 留言评论 如何规避xss攻击? 总结 什 ...
- MFC入门(三)-- MFC图片/文字控件(循环显示文字和图片的小程序)
惯例附上前几个博客的链接: MFC入门(一)简单配置:http://blog.csdn.net/zmdsjtu/article/details/52311107 MFC入门(二)读取输入字符:http ...
- AGC001E BBQ Hard 组合、递推
传送门 题意:给出长度为$N$的两个正整数序列$A_i,B_i$,求$\sum\limits_{i=1}^N \sum\limits_{j=i+1}^N C_{A_i+A_j+B_i+B_j}^{A_ ...
- 坑爹的InetAddress getLocalHost函数
今天在跑dubbo 的 DemoService 2.5.4-SNAPSHOT版本的时候,遇到到一个奇怪的问题.consumer怎么都连接不上provider的服务.最后才发现是由于dubbo自 己实现 ...
- java内存模型与volatile变量与Atomic的compareAndSet
java分主内存和工作内存, 主内存是线程共享的, 工作内存是每个线程独有的. java对主内存的操作是通过工作内存间接完成的: 先拷贝主内存变量值到工作内存, 在工作内存操作这个变量的副本, 完成后 ...
- jdbc操作根据bean类自动组装sql,天啦,我感觉我实现了hibernate
场景:需要将从ODPS数仓中计算得到的大额可疑交易信息导入到业务系统的mysql中供业务系统审核.最简单的方式是用阿里云的组件自动进行数据同步了.但是本系统是开放是为了产品化,要保证不同环境的可移植性 ...
- Oracle_安装说明
1.先到Oracle官网上下载11g oracle Database 11g 第 2 版 (11.2.0.1.0) 标准版.标准版 1 以及企业版 适用于 Microsoft Windows (x64 ...
- win8系统本地服务网络受限cpu占用率过高解决方案
今天更新软件时突然就打不开软件了,接着cpu就飙升. 打开任务管理器看到是“本地服务网络受限”这么一个东西占用的cpu最高. 在网上找到的解决方案无效的: 1.关闭家庭组(服务里的homegroup· ...
- 《Linux内核设计与实现》 第三章学习笔记
一.进程 1.进程就是处于执行期的程序(目标码存放在某种存储介质上).但进程并不仅仅局限于一段可执行程序代码,通常进程还要包含其他资源.执行线程,简称线程(thread),是在进程中活动的对象. 2. ...
- how are you
#include<stdio.h> int main(){ char sentence[100]; int len=0,j,wordlen=0; gets(sentence ...