• Gevent的协程实现原理


    之前之所以看greenlet的代码实现,主要就是想要看看gevent库的实现代码。

    。。

    然后知道了gevent的协程是基于greenlet来实现的。。。所以就又先去看了看greenlet的实现。。。

    这里就不说greenlet的详细实现了。关键就是栈数据的复制拷贝,栈指针的位移。

    。。


    由于gevent带有自己的I/O以及定时循环,所以它对greenlet又加了一层的扩展。。

    这里我们用例如以下的代码来举样例,然后再来详细的分析gevent是怎样扩展greenlet的吧:

    import gevent
    
    def hello(fname):
    	print "hello : ", fname
    	gevent.sleep(0)
    	print "12321 : ", fname
    
    
    task1 = gevent.spawn(hello, "fjs1")
    task2 = gevent.spawn(hello, "fjs2")
    
    task1.join()

    这段代码的输出例如以下:



    嗯,那么闲来看看spawn方法是怎样创建协程的吧:

        #类方法,这个说白了gevent提供的一层构造
        @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)  #先构造greenlet对象
            g.start() #调用start方法,相当于在hub对象的loop上注冊回调,这个回调的作用就是调用当前greenlet的switch切换到这个greenlet的运行
            return g

    这种方法是一个类方法,用于创建一个Greenlet,只是这个要注意。当前这个greenlet已经不是前面提到的greenlet库中定义的那样了,其做了一层简单的扩展。

    来看看构造函数:

    #继承了greenlet,相当于是过扩展了一些功能
    class Greenlet(greenlet):
        """A light-weight cooperatively-scheduled execution unit."""
    
        def __init__(self, run=None, *args, **kwargs):
            hub = get_hub()
            greenlet.__init__(self, parent=hub)   #这里将全部创建的greenlet对象的parent都指向了这个唯一的hub对象
            if run is not None:
                self._run = run  #记录run信息
            self.args = args
            self.kwargs = kwargs
            self._links = deque()
            self.value = None
            self._exception = _NONE
            self._notifier = None
            self._start_event = None

    这里直接继承了greenlet库中greenlet的定义。然后在构造函数中有比較重要的地方,能够看到,全部构造出来的协程的parent都将会指向一个名字叫hub的主协程。。

    这个非常关键。它就是整个gevent的主循环协程,全部创建的业务协程的执行都要依赖于它的调度和管理。。

    好了。在上面spawn过程中。最后还调用了start方法启动协程。那么我们来看看这种方法的定义吧:

    #事实上这个主要是在hub对象的loop上面挂起一个要运行的回调,而这个回调的功能就是切换到这个greenlet的运行
        def start(self):
            """Schedule the greenlet to run in this loop iteration"""
            if self._start_event is None:
                #事实上这个仅仅是在hub的loop上面挂起一个回调,然后在hub的loop里面会运行这个回调
                self._start_event = self.parent.loop.run_callback(self.switch)  #在hub对象的loop里面调用当前greenlet的switch回调,開始run方法的运行

    代码还是非常easy。事实上无非就是在parent也就是hub的loop上面注冊了一个回调,而这个回调就是当前这个协程的switch方法,。。那么等到这个回调被运行的时候,那么也就是開始这个协程的运行的时候。

    。。

    这里我们先不去看hub以及它的loop的实现。

    。。就先将其理解为主循环,管理全部的回调。定时,以及I/O事件。

    。。


    嗯,接下来来看看join方法的实现吧。

    熟悉多线程的都知道。在多线程环境下,join方法就是堵塞当前线程。直到join的目的线程返回了为止。

    。当然这里就不是线程了。变成了协程。

    。。。

    来看看这个join方法的代码吧:

        #将当前运行环境挂起,知道join的greenlet运行完了
        def join(self, timeout=None):
            """Wait until the greenlet finishes or *timeout* expires.
            Return ``None`` regardless.
            """
            if self.ready(): #假设都已经跑完了,那么直接返回吧
                return
            else:
                switch = getcurrent().switch  #获取当前greenlet的switch
                self.rawlink(switch)   #注冊当前环境greenlet的回调,那么以后在这个要等待的greenlet运行完后。将会回调这个
                try:
                    t = Timeout.start_new(timeout)  #创建一个timer对象
                    try:
                        result = self.parent.switch()  #停止当前环境greenlet的运行,调度hub运行
                        assert result is self, 'Invalid switch into Greenlet.join(): %r' % (result, )
                    finally:
                        t.cancel()  #取消timeout
                except Timeout:
                    self.unlink(switch)  #在挂起的回调中去除
                    if sys.exc_info()[1] is not t:
                        raise
                except:
                    self.unlink(switch)
                    raise
    

    首先调用getCurrent方法来获取当前环境的协程,然后获取它的switch方法,将其放置到要join的协程的回调队列里面,当这个要join的协程执行完了之后,将会调用这些回调。这样,就能够恢复当前协程的执行了。。。

    在以下我们能够看到。调用了parent也就是hub的switch方法。切换到hub的运行,这个里面将会開始要join的协程的运行,这里并非直接切换到join的协程的运行。。这点须要注意。。

    另外,gevent自己的greenlet的定义增加了run方法,也就是每次运行都将会从这里開始。

    。。代码例如以下:

        #当前greenlet的运行部分,事实上就是调用传进来的函数。然后运行完了之后再调用那些挂起的回调
        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)

    在运行完了之后,将会调用_report_result方法来运行全部挂在这个协程上面的回调函数,这样对于上面join挂起的回调,就会在这里得到运行,从而让join方法返回继续运行,这样join方法的实现也就比較的清楚了。。事实上还算是比較简单的。。。另外对于怎样运行挂起在这个协程上的回调,比如join的回调,还是比較有讲究的。并非马上在当前协程中运行。而是在hub的loop上挂起一个回调,嗯,代码例如以下:
        #这个主要是为了在hub的loop中挂起回调,用于运行当前这个greenlet全部挂起的回调
        #这里也不是马上运行这些在这个greenlet上面挂起的回调,而是运行继续挂到loop的回调上面去。这样能够让当前协程尽快返回
        #并且假设就在当前协程运行这些回调会出问题,由于假设回调带有别的协程的switch方法,那么switch之后。就再也回不到这个协程继续运行别的回调了
        #而在loop上面运行这些回调,也就是hub上,运行这些回调,即使切换到别的协程,以后也会迟早回到hub上继续运行,所以能保证回调能全部运行完。。
        def _report_result(self, result):
            self._exception = None
            self.value = result
            if self._links and not self._notifier:
                self._notifier = self.parent.loop.run_callback(self._notify_links)

    至于为什么这么大费周章。上面的凝视应该说的非常清楚了吧。。。


    好了,接下来再来分析一下sleep的实现,代码例如以下:

    #事实上sleep的主要目的就是将当前的运行切换出去,回到hub的主循环
    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()  #获取hub对象
        loop = hub.loop  #获取hub的loop对象
        if seconds <= 0:  #假设这里并没有时间
            waiter = Waiter() #创建waiter对象。主要是为了维护当前greenlet与hub之间的切换
            loop.run_callback(waiter.switch)   #在loop上面挂起一个回调,事实上就是在loop中再恢复当前sleep的greenlet的运行
            waiter.get()  #在这个里面最基本的功能就是记录当前的greenlet对象。然后将栈切换到hub上面运行
        else:
            hub.wait(loop.timer(seconds, ref=ref))  #带定时的wait
    

    事实上这里分为了两种种类,就是在sleep的时候传入的超时时间。小于等于0的以及大于0的。。。

    对于sleep操作,假设是在多线程的环境里,比如java的sleep,事实上就是堵塞当前的线程。这样子jvm会调度别的线程的执行,而对于gevent,事实上很多其它的是能够理解为当前协程主动的放弃CPU资源,等到以后再执行。

    首先来看看对于超时小于等于零的。事实上原理非常easy,就是进行switch,切换到hub协程的运行,而且在hub的loop上面注冊一个回调。用于切换回到当前协程的运行。。。


    这里有一点须要的注意的就是。并没有直接在代码中体现switch的操作,而是多了一个waiter对象。。。然后在loop上面注冊的回调是waiter的switch方法,然后调用了waiter对象的get方法。。。


    这里看gevent的凝视才知道。waiter对象能够理解为gevent封装的协程之间的协作工具,详细的协程之间的切换都由waiter来做。避免让用户自己的代码涉及到switch操作。由于这样子非常easy出错。。

    。我们来看看waiter的定义吧:

    #事实上这个对象仅仅是为了维护用户greenlet与hub之间的切换关系
    #将会在hub里面注冊当前waiter对象的switch方法作为回调,然后在hub的loop里面将会运行这个回调
    class Waiter(object):
        """A low level communication utility for greenlets.
    
        Wrapper around greenlet's ``switch()`` and ``throw()`` calls that makes them somewhat safer:
    
        * switching will occur only if the waiting greenlet is executing :meth:`get` method currently;
        * any error raised in the greenlet is handled inside :meth:`switch` and :meth:`throw`
        * if :meth:`switch`/:meth:`throw` is called before the receiver calls :meth:`get`, then :class:`Waiter`
          will store the value/exception. The following :meth:`get` will return the value/raise the exception.
    
        The :meth:`switch` and :meth:`throw` methods must only be called from the :class:`Hub` greenlet.
        The :meth:`get` method must be called from a greenlet other than :class:`Hub`.
    
            >>> result = Waiter()
            >>> timer = get_hub().loop.timer(0.1)
            >>> timer.start(result.switch, 'hello from Waiter')
            >>> result.get() # blocks for 0.1 seconds
            'hello from Waiter'
    
        If switch is called before the greenlet gets a chance to call :meth:`get` then
        :class:`Waiter` stores the value.
    
            >>> result = Waiter()
            >>> timer = get_hub().loop.timer(0.1)
            >>> timer.start(result.switch, 'hi from Waiter')
            >>> sleep(0.2)
            >>> result.get() # returns immediatelly without blocking
            'hi from Waiter'
    
        .. warning::
    
            This a limited and dangerous way to communicate between greenlets. It can easily
            leave a greenlet unscheduled forever if used incorrectly. Consider using safer
            :class:`Event`/:class:`AsyncResult`/:class:`Queue` classes.
        """
    
        __slots__ = ['hub', 'greenlet', 'value', '_exception']
    
        def __init__(self, hub=None):
            if hub is None:
                self.hub = get_hub()  #获取顶层hub对象
            else:
                self.hub = hub
            self.greenlet = None
            self.value = None
            self._exception = _NONE
    
        def clear(self):  
            self.greenlet = None
            self.value = None
            self._exception = _NONE
    
        def __str__(self):
            if self._exception is _NONE:
                return '<%s greenlet=%s>' % (type(self).__name__, self.greenlet)
            elif self._exception is None:
                return '<%s greenlet=%s value=%r>' % (type(self).__name__, self.greenlet, self.value)
            else:
                return '<%s greenlet=%s exc_info=%r>' % (type(self).__name__, self.greenlet, self.exc_info)
    
        def ready(self):
            """Return true if and only if it holds a value or an exception"""
            return self._exception is not _NONE
    
        def successful(self):
            """Return true if and only if it is ready and holds a value"""
            return self._exception is None
    
        @property
        def exc_info(self):
            "Holds the exception info passed to :meth:`throw` if :meth:`throw` was called. Otherwise ``None``."
            if self._exception is not _NONE:
                return self._exception
    
        #调度greenlet的运行,这种方法仅仅能在hub的loop里面运行
        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:
                #仅仅能在hub里面调用waiter的switch方法
                assert getcurrent() is self.hub, "Can only use Waiter.switch method from the Hub greenlet"
                switch = greenlet.switch
                try:
                    switch(value)   #恢复记录的greenlet的运行
                except:
                    self.hub.handle_error(switch, *sys.exc_info())
    
        def switch_args(self, *args):
            return self.switch(args)
    
        def throw(self, *throw_args):
            """Switch to the greenlet with the exception. If there's no greenlet, store the exception."""
            greenlet = self.greenlet
            if greenlet is None:
                self._exception = throw_args
            else:
                assert getcurrent() is self.hub, "Can only use Waiter.switch method from the Hub greenlet"
                throw = greenlet.throw
                try:
                    throw(*throw_args)
                except:
                    self.hub.handle_error(throw, *sys.exc_info())
    
        #这个的最基本的作用就是记录要等待的greenlet
        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()  #记录当前的greenlet对象。在hub的loop里面将会调用当前waiter的switch回调,将会恢复这个greenlet的运行
                try:
                    return self.hub.switch()  #切换到hub上面去运行,那么原来那个greenlet的运行到这里就临时中断了,待会switch会这里继续运行
                finally:
                    self.greenlet = None
    
        def __call__(self, source):
            if source.exception is None:
                self.switch(source.value)
            else:
                self.throw(source.exception)
    
        # can also have a debugging version, that wraps the value in a tuple (self, value) in switch()
        # and unwraps it in wait() thus checking that switch() was indeed called

    这个代码应非常好理解,并且凝视都说的非常清楚。

    。。

    比較重要的就是get方法,这种方法将会保存当前运行的协程,然后切换到hub的运行,对于switch方法,将会切换回刚開始的协程的运行。。


    好了,上面介绍了sleep不带超时的实现。

    接下来来看看带超时的实现:

    hub.wait(loop.timer(seconds, ref=ref))  #带定时的wait

    这里首先创建了一个timer对象。这个能够理解为在loop上面注冊了一个超时,接着看代码:

    #用于在loop上面注冊watcher并等待
        def wait(self, watcher):
            waiter = Waiter() #首先创建一个waiter对象
            unique = object() 
            watcher.start(waiter.switch, unique) #当watcher超时的时候将会调用waiter的switch方法
            try:
                result = waiter.get() #调用waiter的get方法,主要是让将当前调用sleep的greenlet切换出去。然后切换到hub的执行
                assert result is unique, 'Invalid switch into %s: %r (expected %r)' % (getcurrent(), result, unique)
            finally:
                watcher.stop()

    依旧是创建waiter对象,以及它的get方法,只是这里要注意的是,将waiter的switch回调是注冊到刚刚创建的timer对象上的,而不是直接注冊到loop上面。这样待会timer超时的时候将会调用回调。恢复sleep的协程的运行。。


    好了。这里gevent的大体上协程,以及切换关系都几乎相同了。

    。。

  • 相关阅读:
    Spring EL Operators example
    Spring EL method invocation example
    Spring EL hello world example
    Spring @PostConstruct and @PreDestroy example
    Spring init-method and destroy-method example
    Define custom @Required-style annotation in Spring
    Spring dependency checking with @Required Annotation
    Spring properties dependency checking
    UESTC 883 方老师与两个串 --二分搜索+DP
    UESTC 882 冬马党 --状压DP
  • 原文地址:https://www.cnblogs.com/yxysuanfa/p/6956429.html
Copyright © 2020-2023  润新知