每天資訊一文帶你詳細瞭解python的協程

菜單

一文帶你詳細瞭解python的協程

想了解更多精彩內容,快來關注一支穿雲

1、概念

1。1 協程

協程:比執行緒更小的執行單元,又稱微執行緒,在單執行緒上執行多個任務,自帶CPU上下文。用函式切換,開銷極小。不透過作業系統排程,沒有程序、執行緒的切換開銷。

那麼執行緒與協程有什麼區別呢?

我們假設把一個

程序

比作我們實際生活中的一個

拉麵館

,負責保持拉麵館執行的

服務員

就是

執行緒

,每個餐桌點菜代表要完成的任務。

當我們用

多執行緒

完成任務時,模式是這樣的:每來一桌的客人,就在那張桌子上安排一個服務員負責,即有多少桌客人就得對應多少個服務員;

而當我們用

協程

來完成任務時,模式卻有所不同了: 就安排一個服務員,來吃飯得有一個

點餐和等菜的過程

,當A在點菜,就去給B服務,B叫了菜在等待,我就去C,當C也在等菜並且A點菜點完了,趕緊到A來服務… …依次類推。

從上面的例子可以看出,

想要使用協程,那麼我們的任務必須有等待

當我們要完成的任務是耗時任務時,比如屬於IO密集型任務時,我們使用協程來執行任務會節省很多的資源(一個服務員和多個服務員的區別),並且可以極大的利用到系統的資源。

協程,是單執行緒下的併發

,又稱微執行緒,英文名Coroutine。是一種使用者態的輕量級執行緒,即協程是由使用者程式自己控制排程的。協程能保留上一次呼叫時的狀態,每次過程重入時,就相當於進入上一次呼叫的狀態,換種說法:進入上一次離開時所處邏輯流的位置,當程式中存在大量不需要CPU的操作時(IO),適用於協程。【在一個執行緒中CPU來回切換執行不同的任務,這種現象就是協程】

協程有極高的執行效率,因為子程式切換不是執行緒切換,而是由程式自身控制,因此,沒有執行緒切換的開銷。

不需要多執行緒的鎖機制,因為只有一個執行緒,也不存在同時寫變數衝突,在協程中控制共享資源不加鎖,只需要判斷狀態就好了,所以執行效率比多執行緒高很多。

因為協程是一個執行緒執行,所以想要利用多核CPU,最簡單的方法是多程序+協程,這樣既充分利用多核,又充分發揮協程的高效率。

那符合什麼條件就能稱之為協程:

1、必須在只有一個單執行緒裡實現併發

2、修改共享資料不需加鎖

3、使用者程式裡自己儲存多個控制流的上下文棧

4、一個協程遇到IO操作自動切換到其它協程

python中對於協程有四個模組,

greenlet、gevent、yield和async來實現切換和儲存執行緒

1。2 yield實現任務切換+儲存執行緒

一文帶你詳細瞭解python的協程

一文帶你詳細瞭解python的協程

執行效果:

一文帶你詳細瞭解python的協程

yield檢測不到IO,無法實現遇到IO自動切換。

1。3 greenlet是手動切換

一文帶你詳細瞭解python的協程

執行效果輸出:

開門走進衛生間

一看拖把放旁邊

飛流直下三千尺

疑是銀河落九天

greenlet只是提供了一種比yield(生成器)更加便捷的切換方式,當切到一個任務執行時如果遇到IO,那就原地阻塞,仍然是沒有解決遇到IO自動切換來提升效率的問題。

1。4 Gevent實現自動切換協程(多協程)

協程的本質就是在單執行緒下,由使用者自己控制一個任務遇到io阻塞了就切換另外一個任務去執行,以此來提升效率。

一般在工作中我們都是

程序+執行緒+協程

的方式來

實現併發

,以達到最好的併發效果。

如果是4核的CPU,一般起5個程序,每個程序中20個執行緒(5倍CPU數量),每個執行緒可以起500個協程,大規模爬取頁面的時候,等待網路延遲的時間的時候,我們就可以用協程去實現併發。併發數量=5

20

500從而達到5000個併發,這是一般一個4個CPU的機器最大的併發數。nginx在負載均衡的時候最大承載量是5w個。

單執行緒裡的這20個任務的程式碼通常既有計算操作又有阻塞操作,我們完全可以在執行任務1時遇到阻塞,就利用阻塞的時間去執行任務2。。。如此,才能提高效率,這就用到了Gevent模組。

Gevent(自動切換,由於切換是在IO操作時自動完成,所以gevent需要修改Python自帶的一些標準庫,這一過程在啟動時透過monkey patch完成)。

一文帶你詳細瞭解python的協程

一文帶你詳細瞭解python的協程

一文帶你詳細瞭解python的協程

monkey.patch_all() 一定要放到匯入requests模組之前,否則gevent無法識別requests的阻塞。

1。5 async實現協程

一文帶你詳細瞭解python的協程

一文帶你詳細瞭解python的協程

一文帶你詳細瞭解python的協程

3、關鍵字:yield

3。1 yield表示式

yield

相當於return,只不過return是終結函式並返回一個值,而yield是先把值返回並把函式掛起來,以後還會執行yield以下的語句。

一文帶你詳細瞭解python的協程

輸出:

一文帶你詳細瞭解python的協程

1、第一次ti呼叫next函式時,進入foo函式,遇到yield就把count=0返回,並把foo函式掛起

2、在for迴圈中再次呼叫next函式時,就開始執行yield後面的賦值語句,由於沒有接收到值就預設為None,所以res=None

3、然後接著執行賦值語句後面的列印語句和if判斷,由於res為None所以執行count +=1,此時count值為1

4、再次遇到yield,返回1,並把foo函式掛起。

5、send函式是可以給yield生成器傳參的,執行send函式時會預設執行一次next函式,原理同上。

到這裡你可能就明白yield和return的關係和區別了,帶yield的函式是一個生成器,而不是一個函數了,這個生成器有一個函式就是next函式,next就相當於“下一步”生成哪個數,這一次的next開始的地方是接著上一次的next停止的地方執行的,所以呼叫next的時候,生成器並不會從foo函式的開始執行,只是接著上一步停止的地方開始,然後遇到yield後,return出要生成的數,此步就結束。

為什麼用這個生成器,是因為如果用List的話,會佔用更大的空間,比如說取0,1,2,3,4,5,6…………1000

你可能會這樣:

for i in range(1000):

print(i)

這個時候range(1000)就預設生成一個含有1000個數的list了,所以很佔記憶體。

這個時候你可以用剛才的yield組合成生成器進行實現:

一文帶你詳細瞭解python的協程

但這個由於每次都要呼叫函式foo,所以比較耗時間。【這就是用時間換空間】

4、關鍵字:async/await

asyncio

是用來編寫

併發

程式碼的庫,使用

async/await

語法。

asyncio 被用作多個提供高效能 Python 非同步框架的基礎,包括網路和網站服務,資料庫連線庫,分散式任務佇列等等。

asyncio 往往是構建 IO 密集型和高層級 結構化 網路程式碼的最佳選擇。

正常的函式在執行時是不會中斷的,所以你要寫一個能夠中斷的函式,就需要新增async關鍵。

async 用來宣告一個函式為非同步函式

,非同步函式的特點是能在函式執行過程中掛起,去執行其他非同步函式,等到掛起條件(假設掛起條件是asyncio。sleep(5))消失後,也就是5秒到了再回來執行。

await 用來用來宣告程式掛起

,比如非同步程式執行到某一步時需要等待的時間很長,就將此掛起,去執行其他的非同步程式。await 後面只能跟非同步程式或有await屬性的物件,因為非同步程式與一般程式不同。假設有兩個非同步函式async a,async b,a中的某一步有await,當程式碰到關鍵字await b()後,非同步程式掛起後去執行另一個非同步b程式,就是從函式內部跳出去執行其他函式,當掛起條件消失後,不管b是否執行完,要馬上從b程式中跳出來,回到原程式執行原來的操作。如果await後面跟的b函式不是非同步函式,那麼操作就只能等b執行完再返回,無法在b執行的過程中返回。如果要在b執行完才返回,也就不需要用await關鍵字了,直接呼叫b函式就行。所以這就需要await後面跟的是非同步函數了。在一個非同步函式中,可以不止一次掛起,也就是可以用多個await。

可以使用async、await來實現協程的併發

,下面以一個爬蟲例子來說明:

一文帶你詳細瞭解python的協程

一文帶你詳細瞭解python的協程

一文帶你詳細瞭解python的協程

一文帶你詳細瞭解python的協程

一文帶你詳細瞭解python的協程

相對來說還是使用

async執行效率高些

一文帶你詳細瞭解python的協程