• 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.timerwatcher注册一个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

  • 相关阅读:
    程序员们,让你的孩子当个网页工程师吧!
    罗永浩:锤子手机一共卖了12万部(但计划50万)……我已经交出微博密码……(老罗想通了:-))
    母婴市场两万亿,我独钟情于尿布?已获千万级天使的垂直电商“尿布师”,其商业逻辑是这样的
    梵高眼里的《星空》究竟有多美,利用机器学习和图像处理来扩展整幅画的全局景象~
    编程王道,唯“慢”不破
    BAT,你好!字幕组,再见!——也许要跟美剧说再见了~
    Google想出了一个决定人员晋升的算法,然后就没有然后了......
    程序员需要经纪人吗?10x 最好的程序员其生产力相当于同行的 10 倍~
    比特币 Bitcoin 是什么,我勒个去,哈耶克果然超前——货币的非国有化,容我思量一下【转载+整理】
    2014年10月底/终于/HTML5定稿……/技术从来不会成为发展的绝对瓶颈/反而商业成了无法逾越的鸿沟【转载+整理】
  • 原文地址:https://www.cnblogs.com/piperck/p/5650167.html
Copyright © 2020-2023  润新知