本篇文章給大家分享的內容是深入瞭解python中的協程函數 ,有著一定的參考價值,有需要的朋友可以參考一下
概念:
根據維基百科給出的定義,“協程 是為非搶佔式多任務產生子程式的電腦程式組件,協程允許不同進入點在不同位置暫停或開始執行程式”。從技術的角度來說,“協程就是你可以暫停執行的函數”。如果你把它理解成“就像產生器一樣”,那麼你就想對了。
協程,又稱為微線程,看上去像是子程式,但是它和子程式又不太一樣,它在執行的過程中,可以在中斷當前的子程式後去執行別的子程式,再返回來執行之前的子程式,但是它的相關資訊還是之前的。
協程不同於線程,線程是搶佔式的調度,而協程是協同式的調度,協程需要自己做調度。
子程式調用總是一個入口,一次返回,調用順序是明確的。而協程的調用和子程式不同。協程看上去也是子程式,但執行過程中,在子程式內部可中斷,然後轉而執行別的子程式,在適當的時候再返回來接著執行。
協程的優點:
產生器實現協程原理
樣本:
def func(n): index=0 if index<=n: c=yield 1 print("task------{}".format(c)) index+=1f=func(3)n=next(f)print(n)try: n=f.send(5)#程式就直接結束了 print("n是{}".format(n))except StopIteration as e: pass
輸出列印:1task------5
解釋說明:
很明顯func是一個產生器,send方法有一個參數,該參數指定的是上一次被掛起的yield語句的傳回值。
send需要做異常處理。
總的來說,send方法和next方法唯一的區別是在執行send方法會首先把上一次掛起的yield語句的傳回值通過參數設定,從而實現與產生器方法的互動。但是需要注意,在一個產生器對象沒有執行next方法之前,由於沒有yield語句被掛起,所以執行send方法會報錯。
send方法的參數為None時,它與next方法完全等價。
產生器實現生產者和消費者模式:
def cunsumer(): while True: n=yield 3 if not n: return print('cunsumer{}'.format(n))def product(c): c.send(None) n=0 while n<5: n=n+1 r=c.send(n) print("product{}".format(r)) c.close()c=cunsumer()product(c)
列印:cunsumer1product3cunsumer2product3cunsumer3product3cunsumer4product3cunsumer5product3
解釋說明:
在生產者裡先執行了 c.send(None),目的是先讓消費者掛起,再用send傳值,第一次傳1,消費者那裡列印1,生產者列印r是消費者yield後面的值。
greenlet 的引入
雖然CPython(標準Python)能夠通過產生器來實現協程,但使用起來還並不是很方便。
與此同時,Python的一個衍生版 Stackless Python實現了原生的協程,它更利於使用。
於是,大家開始將 Stackless 中關於協程的代碼 單獨拿出來做成了CPython的擴充包。
這就是 greenlet 的由來,因此 greenlet 是底層實現了原生協程的 C擴充庫。
代碼示意:
from greenlet import greenletimport randomimport timedef Producer(): while True: item = random.randint(0,10) print("生產了{}".format(item)) c.switch(item)#切換到消費者,並將item傳入消費者 time.sleep(1)def consumer(): print('我先執行') #p.switch() while True: item = p.switch()#切換到生產者,並且等待生產者傳入item print('消費了{}'.format(item))c = greenlet(consumer)#將一個普通函數變成一個協程p = greenlet(Producer)c.switch()#讓消費者先進入暫停狀態(只有恢複了才能接收資料)
greenlet 的價值:
高效能的原生協程
語義更加明確的顯式切換
直接將函數封裝成協程,保持原有代碼風格
gevent協程
雖然,我們有了 基於 epoll 的回調式編程模式,但是卻難以使用。
即使我們可以通過配合 產生器協程 進行複雜的封裝,以簡化編程難度。
但是仍然有一個大的問題: 封裝難度大,現有代碼幾乎完全要重寫
gevent,通過封裝了 libev(基於epoll) 和 greenlet 兩個庫。
幫我們做好封裝,允許我們以類似於線程的方式使用協程。
以至於我們幾乎不用重寫原來的代碼就能充分利用 epoll 和 協程 威力。
代碼示意:
from gevent import monkey;monkey.patch_all()#會把python標準庫當中一些阻塞操作變成非阻塞import geventdef test1(): print("11") gevent.sleep(4)#類比爬蟲請求阻塞 print("33")def test2(): print("22") gevent.sleep(4) print("44")gevent.joinall([gevent.spawn(test1),gevent.spawn(test2)])#joinall 阻塞當前協程,執行給定的greenlet#spawn 啟動協程,參數就是函數的名字
gevent 的價值:
遇到阻塞就切換到另一個協程繼續執行 !
使用基於 epoll 的 libev 來避開阻塞。
使用基於 gevent 的 高效協程 來切換執行。
只在遇到阻塞的時候切換,沒有輪需的開銷,也沒有線程的開銷。
gevent實現並發伺服器
from gevent import monkey;monkey.patch_all() #建議放在首行,會把python標準庫當中一些阻塞操作變成非阻塞import geventimport socketserver=socket.socket()server.bind(('',6666))server.listen(5)print("開始監聽")def readable(con,addr): print("用戶端{}接入".format(addr)) while True: data=con.recv(1024) if data: print(data) else: con.close() breakwhile True: con,addr=server.accept() gevent.spawn(readable,con,addr)#將readable函數變為協程,並且把con和addr傳入其中。
gevent 協程通訊
gevent也有自己的隊列。使用方式和進線程基本一樣。
基於gevent和隊列的生產者和消費者模式
from gevent import monkey;monkey.patch_all()import geventfrom gevent.queue import Queueimport randomdef producter(queue): while True: item=random.randint(0,99) print('生產了{}'.format(item)) queue.put(item) gevent.sleep(1)def comuser(queue): while True: item=queue.get() print('消費了{}'.format(item))queue=Queue()p=gevent.spawn(producter,queue)c=gevent.spawn(comuser,queue)gevent.joinall([p,c])
列印:生產了33消費了33生產了95消費了95生產了92消費了92...
相關推薦:
python中多進程+協程的使用
python中協程
Python 協程的詳細用法和例子
python 協程樣本