协程 / Coroutine
目录
协程是在一个线程执行过程中可以在一个子程序的预定或者随机位置中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。它本身是一种特殊的子程序或者称作函数。
一个程序可以包含多个协程,可以对比与一个进程包含多个线程。我们知道多个线程相对独立,有自己的上下文,切换受系统控制;而协程也相对独立,有自己的上下文,但是其切换由自己控制,由当前协程切换到其他协程由当前协程来控制。
下面以一个例子介绍一个简单的协程实现,
首先,模拟生产者和消费者模型,建立一个消费者函数,接收一个参数为传入的生产者,初始时使用next函数或send(None)来启动,然后连续7次调用send,将程序切入生产者answer,获取结果,最后调用close或send(None)来结束协程。
1 import time 2 3 4 def ask(a): 5 next(a) # Start generator 6 # a.send(None) 7 n = 0 8 while n < 7: # Ask certain number questions then exit. 9 print('Ask: Try to ask question %d' % n) 10 r = a.send(n) # Send Ques number (ask question), receive is r 11 print('Ask: Received answer <%s>' % r) 12 n += 1 13 a.close() # End loop 14 # try: 15 # a.send(None) 16 # except StopIteration as e: 17 # pass
接下来定义一个生产者answer,生产者会不停返回结果,除非收到None或被调用close函数从而结束。
1 def answer(): # Answer generator 2 ans = '' # First answer for generator start 3 while True: 4 qus = yield ans # Return answer 5 if qus is None: 6 return 7 print('Answer: Received question %s' % qus) 8 time.sleep(1) 9 ans = 'Best answer' 10 11 ask(answer())
运行得到结果,
Ask: Try to ask question 0 Answer: Received question 0 Ask: Received answer <Best answer> Ask: Try to ask question 1 Answer: Received question 1 Ask: Received answer <Best answer> Ask: Try to ask question 2 Answer: Received question 2 Ask: Received answer <Best answer> Ask: Try to ask question 3 Answer: Received question 3 Ask: Received answer <Best answer> Ask: Try to ask question 4 Answer: Received question 4 Ask: Received answer <Best answer> Ask: Try to ask question 5 Answer: Received question 5 Ask: Received answer <Best answer> Ask: Try to ask question 6 Answer: Received question 6 Ask: Received answer <Best answer>
可以看到,ask和answer之间完成了协作性任务,同一时间*一个线程在执行,不存在线程的切换。
在Python中,生成器和协程总是难以区别,为此,在Python3.5之后,引入了新的关键字async和await,用于将普通的函数或生成器包装成为异步的函数和生成器。
下面用代码展示如何使用生成器和协程完成一个异步操作,
完整代码
1 #!/usr/bin/python 2 # ============================================================= 3 # File Name: gene_to_coro.py 4 # Author: LI Ke 5 # Created Time: 1/29/2018 15:34:50 6 # ============================================================= 7 8 9 print('-------- Generator ----------') 10 11 def switch_1(): 12 print('Switch_1: Start') 13 yield 14 print('Switch_1: Stop') 15 16 17 def switch_2(): 18 print('Switch_2: Start') 19 yield 20 print('Switch_2: Stop') 21 22 a = switch_1() 23 b = switch_2() 24 a.send(None) 25 b.send(None) 26 try: 27 b.send(None) 28 except StopIteration as e: 29 re = e.value 30 31 try: 32 a.send(None) 33 except StopIteration as e: 34 re = e.value 35 36 print('-------- Async Coro ----------') 37 38 async def switch_1(): 39 print('Switch_1: Start') 40 await switch_2() 41 print('Switch_1: Stop') 42 43 async def switch_2(): 44 print('Switch_2: Start') 45 print('Switch_2: Stop') 46 47 a = switch_1() 48 try: 49 a.send(None) 50 except StopIteration as e: 51 re = e.value
分段解释
首先利用生成器来完成一个异步操作,定义两个生成器,分别在启动后yield出当前环境,
1 print('-------- Generator ----------') 2 3 def switch_1(): 4 print('Switch_1: Start') 5 yield 6 print('Switch_1: Stop') 7 8 9 def switch_2(): 10 print('Switch_2: Start') 11 yield 12 print('Switch_2: Stop')
完成生成器后,首先分别实例化两个生成器,并利用send(None)进行启动,启动a后再启动b,随后再切入b中完成剩余操作,当b完成后捕获StopIteration异常,并再次切入a中完成后续的操作。
1 a = switch_1() 2 b = switch_2() 3 a.send(None) 4 b.send(None) 5 try: 6 b.send(None) 7 except StopIteration as e: 8 re = e.value 9 10 try: 11 a.send(None) 12 except StopIteration as e: 13 re = e.value
最终运行结果为,
-------- Generator ----------
Switch_1: Start
Switch_2: Start
Switch_2: Stop
Switch_1: Stop
可以看到,利用生成器完成了一个预先设定好的运行流程,仅仅利用单线程完成了一个异步切换的协作式任务。
可是上面的方式存在一个问题,即整个程序的结构十分松散,逻辑上难以理清,因此下面用新增的关键字async和await来完成一个更加符合思维逻辑的异步流程。
首先定义两个异步协程,在协程1中,当协程1开始后,利用await显式地切换至协程2中,当协程2完成后,又继续执行协程1中的操作,整个协程异步的工作顺序在协程内便完成,因此在外部仅需要启动协程1即可。
1 print('-------- Async Coro ----------') 2 3 async def switch_1(): 4 print('Switch_1: Start') 5 await switch_2() 6 print('Switch_1: Stop') 7 8 async def switch_2(): 9 print('Switch_2: Start') 10 print('Switch_2: Stop') 11 12 a = switch_1() 13 try: 14 a.send(None) 15 except StopIteration as e: 16 re = e.value
最后得到的结果与前面利用生成器方式得到的结果相同,但却以一种更加清晰的方式完成了异步编程。
-------- Async Coro ----------
Switch_1: Start
Switch_2: Start
Switch_2: Stop
Switch_1: Stop