python中的協程及實現
1.協程的概念:
協程是一種使用者態的輕量級執行緒。協程擁有自己的暫存器上下文和棧。
協程排程切換時,將暫存器上下文和棧儲存到其他地方,在切換回來的時候,恢復先前儲存的暫存器上下文和棧。
因此,協程能保留上一次呼叫時的狀態(即所有區域性狀態的一個特定組合),每當程式切換回來時,就進入上一次離開時程式所處的程式碼段。
綜合起來,協程的定義就是:
- 必須在只有一個單執行緒裡實現併發
- 修改共享資料不需加鎖
- 使用者程式裡儲存多個控制流的上下文棧
- 一個協程遇到IO操作自動切換到其它協程
2.yield實現的協程
傳統的生產者-消費者模型是一個執行緒生成訊息,一個執行緒取得訊息,能過鎖機制控制佇列和等待,但一不小心就有可能死鎖。
如果改用協程,生產者生產訊息後,直接通過yield跳轉到消費者開始執行,待消費者執行完畢後,切換加生產者繼續生產,效率較高。
程式碼如下:
import time
def consumer():
"""
使用yield生成一個generator生成器
:return:
"""
r = " "
while True:
# yield接收到變數r,處理之後再把結果返回。函式執行到這一步的時候,函式會停留在這一行上,
#當別的函式執行next()語句或者generator.send()語句來啟用這一句,本函式就會
#從yield程式碼的下一行開始繼續執行,直到下一次程式迴圈到yield這裡。
n = yield r
print("[consumer]<-- %s" % n)
time.sleep(1)
r = "ok"
def producer(c):
next(c) #啟動呼叫consumer()函式中的生成器
n = 0
while n < 10:
n += 1
print("[producer]-->%s" % n)
#生產者生產產品,通過c.send()把程式切換到consumer函式執行
cr = c.send(n)
print("[producer] consumer return:%s" % cr)
c.close()
if __name__ == "__main__":
c1 = consumer()
producer(c1)
執行結果:
[producer]--> 1
[consumer]<-- 1
[producer] consumer return:ok
[producer]--> 2
[consumer]<-- 2
[producer] consumer return:ok
[producer]--> 3
[consumer]<-- 3
[producer] consumer return:ok
... #中間省略
[producer]--> 9
[consumer]<-- 9
[producer] consumer return:ok
[producer]--> 10
[consumer]<-- 10
[producer] consumer return:ok
整個流程是由一個執行緒執行,producer和consumer協作完成任務,所以稱為協程,而不是執行緒中的搶佔式多工。
基於協程的定義,剛才使用yield實現的協程並不算合格的協程。
3.由greenlet模組實現的協程
greenlet機制的主要思想是:生成器函式或者協程函式中的yield語句掛起函式的執行,直到稍後使用next()或send()操作進行恢復主止。可以使用一個排程器迴圈在一組生成器函式之間協作多個任務。greenlet是python中實現協程的一個模組。
使用方式 :
from greenlet import greenlet
import time
def func1():
print("func1,ok1---->",time.ctime())
gr2.switch() #程式會切換到func2執行
time.sleep(5) #休眠5s
print("func1,ok2---->",time.ctime())
gr2.switch() #程式又會切換到func2執行
def func2():
print("func2,ok1---->",time.ctime())
gr1.switch() #func2執行到這裡會切換回func1執行
time.sleep(3) #休眠3s
print("func2,ok2---->",time.ctime())
gr1=greenlet(func1)
gr2=greenlet(func2)
gr1.switch()
程式執行流程:
1.程式先執行func1,列印第一句話。
2.func1執行到gr2.switch()這裡時,會切換到func2執行,func2函式列印第一句話。
3.func2執行到gr1.switch()這裡時,又切換回func1函式的time.sleep(5)執行,func1函式會休眠5s。
4.func1先列印第二句話,執行到gr2.switch()這一句時,再次切換回func2函式。
5.func2函式休眠3s,列印func2函式的第二句話,程式執行完畢。
程式執行結果:
func1,ok1----> Fri Jul 21 16:27:11 2017
func2,ok1----> Fri Jul 21 16:27:11 2017
func1,ok2----> Fri Jul 21 16:27:16 2017
func2,ok2----> Fri Jul 21 16:27:19 2017
4.基於greenlet框架,gevent模組實現協程
python通過yield提供了對協程的基本支援,但是不完全。第三方的gevent模組提供了協程支援。
gevent是第三方庫,通過greenlet實現協程。
當一個greenlet遇到IO操作時,比如訪問網路,就自動切換到其他的greenlet,等到IO操作完成,再在適當的時候切換回來繼續執行。由於IO操作非常耗時,經常使程式處於等待狀態,有了gevent自動切換協程,就保證總有greenlet在執行,而不是等待IO。
程式碼如下:
import gevent,time
def func1():
print("running in func1--",time.ctime())
time.sleep(2)
print("running in func1 again--",time.ctime())
def func2():
print("running in func2--",time.ctime())
time.sleep(2)
print("running in func2 again--",time.ctime())
t1=time.time()
g1=gevent.spawn(func1)
g2=gevent.spawn(func2)
gevent.joinall([g1,g2])
t2=time.time()
print("cost time:",t2-t1)
程式執行結果:
running in func1-- Fri Jul 21 17:20:17 2017
running in func1 again-- Fri Jul 21 17:20:19 2017
running in func2-- Fri Jul 21 17:20:19 2017
running in func2 again-- Fri Jul 21 17:20:21 2017
cost time: 4.007229328155518
可以看到程式是按順序執行的。修改程式,使用gevent.sleep()使程式按協程方式執行。
修改後的程式碼如下:
import gevent,time
def func1():
print("running in func1--",time.ctime())
gevent.sleep(2)
print("running in func1 again--",time.ctime())
def func2():
print("running in func2--",time.ctime())
gevent.sleep(2)
print("running in func2 again--",time.ctime())
t1=time.time()
g1=gevent.spawn(func1)
g2=gevent.spawn(func2)
gevent.joinall([g1,g2])
t2=time.time()
print("cost time:",t2-t1)
程式執行結果:
running in func1-- Fri Jul 21 17:17:00 2017
running in func2-- Fri Jul 21 17:17:00 2017
running in func1 again-- Fri Jul 21 17:17:02 2017
running in func2 again-- Fri Jul 21 17:17:02 2017
cost time: 2.0051145553588867
這樣,程式會先執行func1接著執行的是func2,再切換回func1執行。
這種方式可以使原本需要4s才能執行完成的程式只需要執行2s就可以了。
gevent.spawn()方法spawn一些任務,然後通過gevent.joinall將任務加入協程執行佇列中等待執行。
5.協程的優點:
無需執行緒上下文切換造成的資源的浪費。
無需原子操作鎖定及同步的開銷。
方便切換控制流,簡化程式設計模型。
高併發及高擴充套件性加低成本:一個CPU支援上萬的協程都可以,於高併發處理。
6.協程的缺點:
無法利用多核資源,協程的本質是單個執行緒,不能同時使用多核CPU。
協程需要與程式配合才能執行在多CPU上。
程式一旦阻塞,會阻塞整個程式碼段。
相關文章
- python網路-多工實現之協程Python
- python中gevent協程庫Python
- Python的協程Python
- Phxrpc協程庫實現RPC
- Python中協程(coroutine)詳解Python
- python 協程與go協程的區別PythonGo
- Python協程與JavaScript協程的對比PythonJavaScript
- 協程在RN中的實踐
- PHP7下的協程實現PHP
- python大佬養成計劃–協程實現TCP連線PythonTCP
- python 協程Python
- Python協程Python
- Golang協程池(workpool)實現Golang
- 學習之路 / goroutine 併發協程池設計及實現Go
- 理解Python的協程(Coroutine)Python
- python——asyncio模組實現協程、非同步程式設計(三)Python非同步程式設計
- 跟羽夏去實現協程
- Python scrapy增量爬取例項及實現過程解析Python
- [轉載] Python中協程的詳細用法和例子Python
- python之協程的那些事Python
- Python協程詳解Python
- Python的類及單例實現Python單例
- Python課程程式碼實現Python
- 聊一聊Unity協程背後的實現原理Unity
- JWT實現過程及應用JWT
- python協程(yield、asyncio標準庫、gevent第三方)、非同步的實現Python非同步
- python協程asyncio的個人理解Python
- 協程實現canvas影像隨機閃爍Canvas隨機
- 使用socket+gevent實現協程併發
- python進階(25)協程Python
- python進階(17)協程Python
- 擴充套件實現Unity協程的完整棧跟蹤套件Unity
- Python中的單例模式的幾種實現方式的及優化Python單例模式優化
- 理解 Go 中的協程(Goroutine)Go
- go的協程及channel與web開發的一點小實踐GoWeb
- Python 非同步程式設計原理篇之新舊協程實現對比Python非同步程式設計
- Python中迭代器的實現Python
- 深入理解Python協程:從基礎到實戰Python
- java中的鎖及實現原理Java