Gevent的协程实现原理

AI权益加码!Claude Code、Cursor等20+工具免费用! 购周边限时加赠Coding Plan Lite,畅享主流AI工具!学习进阶更高效! 阅读详情

之前之所以看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方法的执行

代码还是很简单,其实无非就是在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资源,等到以后再运行。。。

首先来看看对于超时小于等于零的,其实原理很简单,就是进行switch,切换到hub协程的执行,并且在hub的loop上面注册一个回调,用于切换回到当前协程的执行。。。


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


这里看gevent的注释才知道,waiter对象可以理解为gevent封装的协程之间的协作工具,具体的协程之间的切换都由waiter来做,避免让用户自己的代码涉及到switch操作,因为这样子很容易出错。。。我们来看看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的大体上协程,以及切换关系都差不多了。。。。

Python 基础 之 多任务 gevent 协程应用的简单案例,简单实现下载网上文件的功能(urllib,gevent 等) Python 基础 之 多任务 gevent 协程应用的简单案例,简单实现下载网上文件的功能(urllib,gevent 等) 目录 Python 基础 之 多任务 gevent 协程应用的简单案例,简单实现下载网上文件的功能(urllib,gevent 等) 一、简单介绍 协程概念 协程和线程差异 进程、线程、协程对比 二、实现原理 三、注意事项 四、效果预览 五、实现步骤 六、关键代码 一、简单介绍 Python是一种跨平台的计算机程序设计语言。是一种面向对象的... 阅读详情

相关推荐

python 协程 gevent原理与用法分析

本文实例讲述了python 协程 gevent原理与用法。分享给大家供大家参考,具体如下: gevent greenlet已经实现协程,但是这个还的人工切换,是不是觉得太麻烦了,不要捉急,python还有一个比greenlet更强大的并且能够自动切换任务的模块gevent原理是当一个greenlet遇到IO(指的是input output 输入输出,比如网络、文件操作等)操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。 由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在

python--gevent协程协程概念

何为协程    协程,又称微线程。英文名Coroutine。   协程最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。   第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高...

weixin_33762321的博客 156

深入学习 Python Gevent 协程:从基础到实践

Gevent 是一个强大的 Python 协程库,它通过轻量级的协程和高效的事件循环机制,为 Python 提供了高效的并发执行能力。在本文中,我们详细介绍了 Gevent 的安装、基本用法和高级功能,并通过示例程序展示了其在实际编程中的应用。希望这些信息能够帮助你更好地理解和使用 Gevent,进而提升你的 Python 并发编程能力。如果你对 Gevent 感兴趣,不妨深入探索其官方文档和社区资源,以发现更多有趣和实用的功能。孔乙己大叔您的一站式代码技术资源中心。

孔乙己大叔的博客 1410

Python Gevent协程机制

参考资料 http://www.gevent.org/contents.html https://uwsgi-docs-zh.readthedocs.io/zh_CN/latest/Gevent.html Python脚本的执行效率一直来说并不是很高,特别是Python下的多线程机制,长久以来一直被人们诟病。很多人都在思考如何让Python执行的更快一些,其中典型的方式包括: 将复杂的代码转由C语言完成 多进程并发执行 多线程完成IO操作 然后,人们讨论的更多的则是Gevent协程机制。在理解Gevent

西涛offbye-移动全栈技术博客 1012

python——总结协程实现原理gevent

进程:资源分配最小单位 线程:调度最小单位 协程:单线程,不同任务之间调度 gevent:greenlet+IO自动切换(epoll) ———————————————————————— 从下向上分析(具体就不展开了): gevent 实质:greenlet+epoll greenlet:实现了原生的协程(但是不会检测IO,不知道切换到哪个greenlet) epoll:基于libev的快速事件循环...

遇到问题写下来,为了以后方便查啊~ 967

(自学)gevent原理

gevent原理 """ 2020.07.13 1.目的:用web服务器为例gevent原理gevent本质上:单进程单线程实现为多个套接字服务的原理) 2.思路: 1.创建服务器:创建、绑定、、被动、监听、循环创新(新函数实现收发:堵塞以收信息--发) 2.关闭堵塞(.setblocking(False))--会产生错误--try--有异常就是无客户端连接/无数据发过来,无异常就是有交互 3.创建一个列表保存心得套接字--遍历--异常判断try--有异常就是无客户端连接/无数

m0_46267702的博客 525

协程gevent模块的使用

什么是协程?协又叫又称微线程,英文名Coroutine,协程是python个中另外一种实现多任务的方式,只不过比线程更小占用更小执行单元(理解为需要的资源)。 为啥说它是一个执行单元,因为它自带CPU上下文。这样只要在合适gr的时机, 我们可以把一个协程 切换到另一个协程。 只要这个过程中保存或恢复 CPU上下文那么程序还是可以运行的。通俗的描述:协程看做是是线程中的一个特殊的函数,这个函数执行的时候,可以在某个地方暂停,并且可以重新在暂停处,继续运行,协程在进行切换的时候,只需要保存当前协程函数中的一些临

henry_rhy的博客 2382

初探python的gevent协程

gevent Python通过yield提供了对协程的基本支持,但是不完全。而第三方的gevent为Python提供了比较完善的协程支持 gevent是第三方库,通过greenlet实现协程,其基本思想是: 当一个greenlet遇到IO操作时(比如访问网络),就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。由于IO操作非常耗时,经常使程序处于...

dipihuo0431的博客 138

gevent协程

文章目录1、 yield 实现协程1.1 yield 同步执行1.2 启动多个yield模拟consumer并发2 、greenlet实现协程2.1 简单gevent协程例子2.2 gevent 高并发测试2.3 理解gevent的monkey.patch_all()2.2.1 locals()方法2.2.2 gevent 替换非阻塞的模块的思路3、gevent examples3.1 使用协...

pysense的博客 936

python 协程gevent.pool_python并发编程之gevent协程()

协程的含义就不再提,在py2和py3的早期版本中,python协程的主流实现方法是使用gevent模块。由于协程对于操作系统是无感知的,所以其切换需要程序员自己去完成。系列文章基于生成器的简单协程import timedef A():for i in range(100):print("----A---")yield itime.sleep(0.5)def B(c):while True:prin...

weixin_39552317的博客 714

Python协程(gevent模块)

一:前言 协程又称为微线程,纤程。英文名Coroutine:协程是一种用户态的轻量级线程 协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复之前保存的寄存器上下文和栈。因此: 协程能够保留上次一调用时的状态,能够进入上一次离开时所处的逻辑流的位置  协程的好处 无需线程上下文切换的开销 无需原子操作(不会被线程调度机制打断的操作)...

马赛克的博客 1万+

python协程详解,gevent asyncio

python协程详解,gevent asyncio新建模板小书匠#协程的概念 #模块操作协程 # gevent 扩展模块 # asyncio 内置模块 # 基础的语法 1、生成器实现切换 [1] import time def func1(): print(1) yield 1 time.sleep(1) print(2) def func2(...

weixin_34128501的博客 1392

python之协程的那些事

python如何设置多进程(直通车) 协程 基本概念 协程,又称微线程,纤程。英文名Coroutine。协程是一种用户态的轻量级线程。 协程原理 协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次...

IT届的小学生 1255

python基础-协程gevent用法、协程同步异步、协程socket并发例子

协程引入 协程概念 gevent用法 同步异步 协程下的socket并发协程引入本节的主题是基于单线程来实现并发,即只用一个主线程(很明显可利用的cpu只有一个)情况下实现并发,为此我们需要先回顾下并发的本质:切换+保存状态cpu正在运行一个任务,会在两种情况下切走去执行其他的任务(切换由操作系统强制控制),一种情况是该任务发生了阻塞,另外一种情况是该任务计算的时间过长协程的本质就是在单线程下,由用

全干工程师 5755
上一篇: python协程的实现(greenlet源码分析)
下一篇: Gevent源码之loop的实现
fjs_cloud
博客等级 码龄15年 248粉丝 192原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值