• 深入理解协程(一):协程的引入


    原创不易,转载请注明出处

    深入理解协程分为三部分进行讲解:

    • 协程的引入
    • yield from实现协程
    • async/await实现异步协程

    本篇为深入理解协程文章的第一篇。

    什么是协程

    协程:英文叫做 Coroutine,又称微线程,纤程,是一种用户态的轻量级线程。

    本质上是单线程,拥有自己的寄存器上下文和栈。所以能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态。

    与多进程相比,无需线程上下文切换的开销;与多线程相比,无需使用多线程的锁机制。执行效率要高于多进程和多线程。

    最简单的协程

    在Python2.5时引入,是通过生成器(generator)实现的。下面来看一下生成器实现的最简单的协程。

    >>> def coroutine():	# 注释一
            print('-> coroutine started')
            x = yield	# 注释二
            print('-> coroutine received:', x)
        
    >>> c = coroutine()	# 注释三
    >>> c	
    <generator object coroutine at 0x03899230>
    
    >>> next(c)	# 注释四
    -> coroutine started
    
    >>> c.send(1)	# 注释五
    -> coroutine received: 1
    Traceback (most recent call last):
      File "<input>", line 1, in <module>
    StopIteration	# 注释六
    

    注释一:使用生成器函数定义一个协程(函数中需有yield关键字)

    注释二:yield关键字分为左右两边理解,yield右边用于返回数据,左边用于接收用户数据。如果只从用户客户端接收数据,那么yield返回值为None。

    注释三:创建生成器。

    注释四:刚创建的生成器需要预激活,这里调用next()函数对生成器进行预激活。也可使用c.send(None)

    注释五:预激活完成后,使用send()函数传入数据,协程生成器中在yield处会计算出1,程序继续运行直到运行到下一个yield表达式出现或程序终止。

    注释六:由于程序运行到结尾,协程生成器中再无yield关键字,导致生成器像往常一样抛出StopIteration异常。

    协程的四种状态

    • GEN_CREATED

      协程生成器创建完成,等待开始执行。

    • GEN_RUNNING

      解释器正在执行。

    • GEN_SUSPENDED

      在yield表达式处暂停。

    • GEN_CLOSED

      执行结束。

    协程当前的状态可通过inspect模块查询。

    下面举一个产出多个值的例子,以便更好理解协程的行为。

    >>> from inspect import getgeneratorstate
    >>> def coroutine(a):
            print('-> started a=', a)
            b = yield a
            print('-> received: b=', b)
            c = yield a + b
            print('-> received: c=:', c)
        
    >>> x = coroutine(1)
    >>> getgeneratorstate(x)
    'GEN_CREATED'	# 注释一
    
    >>> next(x)		
    -> started a= 1
    1				# 注释二
    >>> x.send(2)	
    -> received: b= 2
    3				# 注释三
    >>> getgeneratorstate(x)
    'GEN_SUSPENDED'		# 注释四
    
    >>> x.send(3)
    Traceback (most recent call last):
    -> received: c=: 3
      File "<input>", line 1, in <module>
    StopIteration		
    
    >>> getgeneratorstate(x)
    'GEN_CLOSED'		# 注释五
    

    注释一:刚创建生成器,协程生成器还处于GEN_CREATED状态(协程未启动)。

    注释二:使用next(x)预激活协程生成器,程序执行到第一个yield暂停,即返回a的值1

    注释三x.send(2)向协程生成器传入值2并赋值给变量b,程序执行到第二个yield暂停,此处返回a+b的值,即为3

    注释四:此时程序在第二个yield处暂停,所以协程生成器处于GEN_SUSPENDED状态。

    注释五:协程生成器抛出异常后,导致协程生成器结束,因此处于GEN_CLOSED状态。

    终止协程

    上面举例的协程都是以抛出异常结束的,其实可以使用close()方法正常结束协程。

    我们将第一示例代码进行修改,

    >>> from inspect import getgeneratorstate
    >>> def coroutine():  
            print('-> coroutine started')
            while True:		# 注释一
                x = yield  
                print('-> coroutine received:', x)
            
    >>> c = coroutine()
    >>> next(c)
    -> coroutine started
    
    >>> c.send(1)
    -> coroutine received: 1
        
    >>> c.send(2)
    -> coroutine received: 2
        
    >>> getgeneratorstate(c)
    'GEN_SUSPENDED'		
    
    >>> c.close()		
    >>> getgeneratorstate(c)
    'GEN_CLOSED'		# 注释二
    

    注释一:此处加入死循环,避免协程生成器终止。

    注释二:可以看出在执行close()之后协程生成器处于GEN_SUSPENDED状态。即协程正常结束。

    异常处理

    throw()方法可以传入异常。请看示例代码:

    >>> class DemoException(Exception):	# 注释一
            '''定义的演示异常类型'''
            pass
    
    >>> def coroutine():
            print('-> coroutine started')
            while True:
                try:
                    x = yield
                except DemoException:	# 注释二
                    print('*** DemoException handled. Continuing...')
                else:
                    print('-> coroutine received:', x)
            raise RuntimeError('This line should never run.')	# 注释三
            
    >>> c = coroutine()
    >>> next(c)
    -> coroutine started
    
    >>> c.send(1)
    -> coroutine received: 1
        
    >>> c.throw(DemoException)	# 注释四
    *** DemoException handled. Continuing...
    

    注释一:自定义异常类型DemoException

    注释二:特殊处理DemoException类型。

    注释三:这一行永远不会执行。

    注释四:使用throw()传入异常类型。

    本篇讲述的为协程最原始的实现方法,虽然实现与异常处理比较繁琐,但确是协程的实现原理,对于真正理解协程大有裨益。

    下篇将与您分享如何使用yield from实现协程。

    在这里插入图片描述

  • 相关阅读:
    jquery 只能输入汉字
    实现鼠标移到某个对象,在旁边显示层。
    jquery 清空页面中radio选项
    oracle 删除表中重复的数据
    jQuery 获取屏幕高度、宽度
    jquery清空from表单中的所有数据
    oracle sql语句大全
    mysql sql语句大全
    Java精品书籍推荐
    小萌库
  • 原文地址:https://www.cnblogs.com/ghostlee/p/12079493.html
Copyright © 2020-2023  润新知