協程,又稱微線程,纖程。英文名Coroutine。python
線程是系統級別的它們由操做系統調度,而協程則是程序級別的由程序根據須要本身調度。在一個線程中會有不少函數,咱們把這些函數稱爲子程序,在子程序執行過程當中能夠中斷去執行別的子程序,而別的子程序也能夠中斷回來繼續執行以前的子程序,這個過程就稱爲協程。也就是說在同一線程內一段代碼在執行過程當中會中斷而後跳轉執行別的代碼,接着在以前中斷的地方繼續開始執行,相似與yield操做。程序員
協程擁有本身的寄存器上下文和棧。協程調度切換時,將寄存器上下文和棧保存到其餘地方,在切回來的時候,恢復先前保存的寄存器上下文和棧,也就是知道屬於本身的變量,這就是爲何生成器在yield以後,能夠繼續使用yield 的變量的緣由。所以:協程能保留上一次調用時的狀態(即全部局部狀態的一個特定組合),每次過程重入時,就至關於進入上一次調用的狀態,換種說法:進入上一次離開時所處邏輯流的位置。編程
協程的優勢:api
(1)無需線程上下文切換的開銷,協程避免了無心義的調度,由此能夠提升性能(但也所以,程序員必須本身承擔調度的責任,同時,協程也失去了標準線程使用多CPU的能力)併發
(2)無需原子操做鎖定及同步的開銷異步
(3)方便切換控制流,簡化編程模型函數
(4)高併發+高擴展性+低成本:一個CPU支持上萬的協程都不是問題。因此很適合用於高併發處理。高併發
協程的缺點:性能
(1)沒法利用多核資源:協程的本質是個單線程,它不能同時將 單個CPU 的多個核用上,協程須要和進程配合才能運行在多CPU上.固然咱們平常所編寫的絕大部分應用都沒有這個必要,除非是cpu密集型應用。優化
(2)進行阻塞(Blocking)操做(如IO時)會阻塞掉整個程序
接下來是用協程實現 生產者,消費者的經典案例:
def consumer(): print("[consumer]:i am hunger , i need food") while True: food = yield if food is None: break print(f"[consumer]: so delicious {food} baozi") print('i see') def producter(): g = consumer() #生成一個消費者對象 i = 0 next(g) #協程必須預激活,走到 上面yield處,而後掛着,等待調用方給它發消息 while i < 5: print(f"[producter]: make food {i} baozi") g.send(i) #使用生成器提供的api send,send中的值就是上面food的值; i += 1 print("[producter]: oh no, i am tried") g.close() #關閉協程,養成良好習慣 producter()
[consumer]:i am hunger , i need food [producter]: make food 0 baozi [consumer]: so delicious 0 baozi [producter]: make food 1 baozi [consumer]: so delicious 1 baozi [producter]: make food 2 baozi [consumer]: so delicious 2 baozi [producter]: make food 3 baozi [consumer]: so delicious 3 baozi [producter]: make food 4 baozi [consumer]: so delicious 4 baozi [producter]: oh no, i am tried
值得注意點是:就算不用調用上面的g.close(),程序雖然會正常退出,可是這個協程並無關閉,並且它的此時的狀態是:GEN_SUSPENDED ;
>>> from demo_product import consumer >>> from inspect import getgeneratorstate >>> g = consumer() >>> getgeneratorstate(g) 'GEN_CREATED' >>> next(g) [consumer]:i am hunger , i need food >>> getgeneratorstate(g) 'GEN_SUSPENDED' >>> g.send(2) [consumer]: so delicious 2 baozi >>> g.send(5) [consumer]: so delicious 5 baozi >>> g.send(None) i see Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration >>> getgeneratorstate(g) 'GEN_CLOSED' >>> g = consumer() >>> next(g) [consumer]:i am hunger , i need food >>> g.close() >>> getgeneratorstate(g) 'GEN_CLOSED'
從上面的代碼中,咱們能夠發現,協程對象是必需要預激活的,當協程激活後,協程在yield處停留。此時可使用三種方式結束協程,一種是經過g.send(None)發送一個None值,而後獲得異常StopIteration,另外一種方式是經過g.close(),這種方式相比來講比較優雅。還有一種方式就是經過g.throw(error),主動拋出一個異常,類型能夠自定;假若 是想要協程返回值,那麼就須要使用g.send(None)以及g.throw(error)這兩種方式了。
下方是經過g.send(None)的方式,結束協程以及獲取協程的返回值;
def consumer(): print("[consumer]:i am hunger , i need food") while True: food = yield if food is None: break print(f"[consumer]: so delicious {food} baozi") return "i see" def producter(): g = consumer() i = 0 next(g) while i < 5: print(f"[producter]: make food {i} baozi") g.send(i) i += 1 print("[producter]: oh no, i am tried") try: g.send(None) except StopIteration as e: res = e.value print(res) if __name__ == "__main__": producter()
其實python還提供一種更簡單的方式獲取協程的返回值,那就是使用關鍵字:yield from ,經過這種方式能夠優雅的處理StopIteration的錯誤,而且獲取錯誤中的返回值,不過這種方式得在委派生成器中使用,經過委派生成器鏈接調用方與子生成器,這個委派生成器則是兩則溝通的橋樑,具體細節我不在此多說,有興趣的能夠深刻了解;下面是yield from基礎演示,它省去了一層for循環。
>>> def gen(): ... for i in range(3): ... yield i ... >>> g = gen() >>> for i in g: ... print(i) ... 0 1 2 >>> def gen(): ... yield from range(3) ... >>> g = gen() >>> for i in g: ... print(i) ... 0 1 2 >>>
總結:可見協程在實現異步上是多麼方便,cpu只須要發出指令集,等待返回結果便可,在這等待的時間內,cpu能夠去幹其餘活,極大的提升了效率。正如《優化python代碼的59個建議》中提到的,第40條,應該經過使用協程進行併發操做。