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

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

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

  • 协程的引入
  • 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实现协程。

在这里插入图片描述

posted @ 2019-12-22 12:40  西加加先生  阅读(481)  评论(0编辑  收藏  举报