python 协程库gevent学习--源码学习(一)
总算还是要来梳理一下这几天深入研究之后学习到的东西了。
这几天一直在看以前跟jd对接的项目写的那个gevent代码。为了查错,基本上深入浅出了一次gevent几个重要部件的实现和其工作的原理。
这里用一个简单demo依次分析运行流程和介绍相关概念最后得出结论:
import geventdef 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.valueelse: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.looploop.error_handler = selftry:loop.run()finally:loop.error_handler = None # break the refcount cycleself.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.greenletif greenlet is None:self.value = valueself._exception = Noneelse:assert getcurrent() is self.hub, "Can only use Waiter.switch method from the Hub greenlet"switch = greenlet.switchtry: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的源码:
@classmethoddef 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 secondsare desired.If *ref* is false, the greenlet running sleep() will not prevent gevent.wait()from exiting."""hub = get_hub()loop = hub.loopif 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.valueelse: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.looploop.error_handler = selftry:loop.run()finally:loop.error_handler = None # break the refcount cycleself.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_eventelse:self._start_event.stop()try:result = self._run(*self.args, **self.kwargs)except:self._report_error(sys.exc_info())returnself._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
转载于:https://www.cnblogs.com/piperck/p/5650167.html
python 协程库gevent学习--源码学习(一)相关推荐
- python 协程库gevent学习 -- 超时、互斥锁(BoundedSemaphore)、local
1.Timeout错误类 晚上在调试调用第三方接口的时候,发现有些接口耗时非常多,觉得应该有个超时接口来限制他们的过长时间的不结束.我开始尝试了requests上面的timeout参数,整个代码流程里 ...
- python 协程库_python协程概念
什么是协程 协程是单线程下的并发,又称微线程,纤程.它是实现多任务的另一种方式,只不过是比线程更小的执行单元.因为它自带CPU的上下文,这样只要在合适的时机,我们可以把一个协程切换到另一个协程.英文名 ...
- python协程库_python中协程的详解(附示例)
本篇文章给大家带来的内容是关于python中协程的详解(附示例),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. 协程,又称微线程,纤程.英文名Coroutine 协程看上去也是子程序 ...
- python 协程池gevent.pool_进程池\线程池,协程,gevent
目录 1. 进程池与线程池 2. 协程 3. gevent 4. 单线程下实现并发的套接字通信 首先写一个基于多线程的套接字 服务端: from socket import * from thread ...
- python 协程库_python 协程库gevent学习--源码学习(一)
总算还是要来梳理一下这几天深入研究之后学习到的东西了. 这几天一直在看以前跟jd对接的项目写的那个gevent代码.为了查错,基本上深入浅出了一次gevent几个重要部件的实现和其工作的原理. 这里用 ...
- python 协程库_python 协程库gevent学习--gevent数据结构及实战(四)
一不留神已经到第四部分了,这一部分继续总结数据结构和常用的gevent类,废话不多说继续. 1.Timeout错误类 晚上在调试调用第三方接口的时候,发现有些接口耗时非常多,觉得应该有个超时接口来限制 ...
- python 协程库_python --- 协程编程(第三方库gevent的使用)
1. 什么是协程? 协程(coroutine),又称微线程.协程不是线程也不是进程,它的上下文关系切换不是由CPU控制,一个协程由当前任务切换到其他任务由当前任务来控制.一个线程可以包含多个协程,对于 ...
- SEAL库之bfv_basics源码学习
本文对bfv_basics.cpp中的注释做翻译并展示examples.cpp的运行结果. SEAL库的安装推荐这篇博客.(不过我安装的是SEAL3.7,和4.0相比少了bgv_basics.cpp) ...
- python until怎么用不了_为何你还不懂得如何使用Python协程
关于我 一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android.Python.Java和Go,这个也是我们团队的主要技术栈. Github:https:/ ...
最新文章
- CentOS的Gearman安装与使用无错版
- 【OpenCV 4开发详解】图像噪声的种类与生成
- linux修改用户名和密码
- 9.Tornado下的一个简易Blog--2013-05-21
- v-bind单向绑定与v-model双向绑定
- python制作二级菜单_Python_简单三级菜单制作
- iOS 钥匙串的基本使用
- 【转】C# split 几种使用方法
- java中使用lua脚本
- ping端口_干货分享:shell脚本批量telnet ip 端口
- CS-- WebService、 windowsService
- eclipse 快捷键
- 解决安装mysql动态库libstdc++.so.6、libc.so.6版本过低问题
- Hibernate.cfg.xml 整理
- 小程序投票帮怎么刷票
- 如何使用ADI公司的AD9833自制任意波形/函数发生器
- 6678与FPGA PCIE调试
- 关于JS按钮倒计时禁用的小Demo
- 二进制计算机代码,二进制代码是什么???
- 你敢信?码农靠倒卖烂水果,融资上亿
热门文章
- iPhone彻底删除的照片能恢复吗,2个找回永久删除照片的方法
- 【AtCoder】【思维】【图论】Splatter Painting(AGC012)
- linux-磁盘分区
- python爬取返利网
- java订单派单规则_重点解读 | 什么是派单?派单的好处有哪些?
- 核磁共振分析处理软件:Nucleomatica iNMR for Mac
- [DSP 日常记录] #1 冯诺依曼结构、哈佛结构与改进型哈佛结构
- MATLAB中nargin函数的用法nargin是用来判断输入变量个数的函数,这样就可以针对不同的情况执行不同的功能。通常可以用它来设定一些默认值。如下例所示: 函数文件 examp.m
- 欧几里德距离的相似度 —— Euclidean Distance-based Similarity
- macos 中先安装了pyqt5再安装opencv出现的QtCore冲突问题