深入理解协程(一):协程的引入
原创不易,转载请注明出处
深入理解协程
分为三部分进行讲解:
- 协程的引入
- 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
实现协程。