協程 / 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
