玩轉python協程的使用gevent,greenlet,yield等

1.進程,線程,協程的關係

1.1一個淺顯的例子,說明三者的邏輯關係

  • 有一個老闆想要開個工廠進行生產某件商品(例如剪子)
  • 他需要花一些財力物力製作一條生產線,這個生產線上有很多的器件以及材料這些所有的 爲了能夠生產剪子而準備的資源稱之爲:進程
  • 只有生產線是不能夠進行生產的,所以老闆的找個工人來進行生產,這個工人能夠利用這些材料最終一步步的將剪子做出來,這個來做事情的工人稱之爲:線程
  • 這個老闆爲了提高生產率,想到3種辦法:
    1. 在這條生產線上多招些工人,一起來做剪子,這樣效率是成倍増長,即單進程 多線程方式
    2. 老闆發現這條生產線上的工人不是越多越好,因爲一條生產線的資源以及材料畢竟有限,所以老闆又花了些財力物力購置了另外一條生產線,然後再招些工人這樣效率又再一步提高了,即多進程 多線程方式
    3. 老闆發現,現在已經有了很多條生產線,並且每條生產線上已經有很多工人了(即程序是多進程的,每個進程中又有多個線程),爲了再次提高效率,老闆想了個損招,規定:如果某個員工在上班時臨時沒事或者再等待某些條件(比如等待另一個工人生產完謀道工序 之後他才能再次工作) ,那麼這個員工就利用這個時間去做其它的事情,那麼也就是說:如果一個線程等待某些條件,可以充分利用這個時間去做其它事情,其實這就是:協程方式

1.2.核心的關係:進程,線程,協程都可以實現多任務模式

  1. 進程是資源分配的單位
  2. 線程是操作系統調度的單位
  3. 進程切換需要的資源很最大,效率很低
  4. 線程切換需要的資源一般,效率一般(當然了在不考慮GIL的情況下)
  5. 協程切換任務資源很小,效率高
  6. 多進程、多線程根據cpu核數不一樣可能是並行的,但是協程是在一個線程中 所以是併發

2.協程的實現與演示

協程,又稱微線程,英文名Coroutine,協同程序(同一個線程裏任務切換協同執行)。如下,在同在一個線程中的某個函數,可以在任何地方保存當前函數的一些臨時變量等信息,然後切換到另外一個函數中執行,注意不是通過調用函數的方式做到的,並且切換的次數以及什麼時候再切換到原來的函數都由開發者自己確定。

2.1.使用yield實現協程。

import time

def work1():
    while True:
        print("----work1---")
        yield
        time.sleep(0.5)

def work2():
    while True:
        print("----work2---")
        yield
        time.sleep(0.5)

def main():
    w1 = work1()
    w2 = work2()
    while True:
        next(w1)
        next(w2)

if __name__ == "__main__":
    main()
'''
----work1---
----work2---
----work1---
----work2---
----work1---
....省略....
'''

2.使用greenlet實現協程

可以使用python中的greenlet模塊中greenlet實現協程工作,如果沒安裝先安裝 庫,這裏是基於pycharm執行的

 pip3 install greenlet  #如果使用pycharm,則現在環境裏安裝該模塊 
from greenlet import greenlet
import time

def test1():
    while True:
        print "---A--"
        gr2.switch()
        time.sleep(0.5)

def test2():
    while True:
        print "---B--"
        gr1.switch()
        time.sleep(0.5)

gr1 = greenlet(test1)
gr2 = greenlet(test2)

#切換到gr1中運行
gr1.switch()

'''
---A--
---B--
---A--
---B--
---A--
---B--
---A--
---B--
'''

 3.使用終極模塊gevent實現協程

         Gevent是一種基於協程的Python網絡庫,它用到Greenlet提供的,封裝了libevent事件循環的高層同步API。它讓開發者在不改變編程習慣的同時,用同步的方式寫異步I/O的代碼。

        greenlet已經實現了協程,但是這個還的使用switch切換,python還有一個比greenlet更強大的並且能夠自動切換任務的模塊那就是gevent.其原理是當一個greenlet遇到IO(指的是input output 輸入輸出,比如網絡、文件操作等)操作時,比如訪問網絡,就自動切換到其他的greenlet,等到IO操作完成,再在適當的時候切換回來繼續執行。 由於IO操作非常耗時,經常使程序處於等待狀態,有了gevent爲我們自動切換協程,就保證總有greenlet在運行,而不是等待IO

import gevent

def f1(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
def f2(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
def f3(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
print("開始執行 ------")
print("------f1 ------")
g1 = gevent.spawn(f1, 5) #創建一個協程任務,其實創建的是一個greenlet對象
print("------f2 ------")
g2 = gevent.spawn(f2, 5)
print("------f3 ------")
g3 = gevent.spawn(f3, 5)
g1.join()  #join()啓動等待g1執行完成
g2.join()
g3.join()

'''開始執行 ------
------f1 ------
------f2 ------
------f3 ------
<Greenlet at 0x23a40197a48: f1(5)> 0
<Greenlet at 0x23a40197a48: f1(5)> 1
<Greenlet at 0x23a40197a48: f1(5)> 2
<Greenlet at 0x23a40197a48: f1(5)> 3
<Greenlet at 0x23a40197a48: f1(5)> 4
<Greenlet at 0x23a40197e48: f2(5)> 0
<Greenlet at 0x23a40197e48: f2(5)> 1
<Greenlet at 0x23a40197e48: f2(5)> 2
<Greenlet at 0x23a40197e48: f2(5)> 3
<Greenlet at 0x23a40197e48: f2(5)> 4
<Greenlet at 0x23a40373048: f3(5)> 0
<Greenlet at 0x23a40373048: f3(5)> 1
<Greenlet at 0x23a40373048: f3(5)> 2
<Greenlet at 0x23a40373048: f3(5)> 3
<Greenlet at 0x23a40373048: f3(5)> 4'''

總結分析:上面3個greenlet是依次運行而不是交替運行,前面一個執行完,後面纔開始執行的。

1.下面通過gevent.sleep()實現交替執行,當個g1處於sleep時,切換到g2或者g3執行。

import gevent ,time

def f1(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        gevent.sleep(0.5)

def f2(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        gevent.sleep(0.5)
def f3(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        gevent.sleep(0.5)  #用來模擬一個耗時操作,注意不是time模塊中的sleep
print("開始執行 ------")
print("------f1 ------")
g1 = gevent.spawn(f1, 5) #創建一個協程任務,其實創建的是一個greenlet對象
print("------f2 ------")
g2 = gevent.spawn(f2, 5)
print("------f3 ------")
g3 = gevent.spawn(f3, 5)
g1.join()  #join()等待g1執行完成
g2.join()
g3.join() #可以使用joinall([])替代

'''
開始執行 ------
------f1 ------
------f2 ------
------f3 ------
<Greenlet at 0x2226aa27a48: f1(5)> 0
<Greenlet at 0x2226aa27e48: f2(5)> 0
<Greenlet at 0x2226abf3048: f3(5)> 0
<Greenlet at 0x2226aa27a48: f1(5)> 1
<Greenlet at 0x2226aa27e48: f2(5)> 1
<Greenlet at 0x2226abf3048: f3(5)> 1
<Greenlet at 0x2226aa27a48: f1(5)> 2
<Greenlet at 0x2226aa27e48: f2(5)> 2
<Greenlet at 0x2226abf3048: f3(5)> 2
<Greenlet at 0x2226aa27a48: f1(5)> 3
<Greenlet at 0x2226aa27e48: f2(5)> 3
<Greenlet at 0x2226abf3048: f3(5)> 3
<Greenlet at 0x2226aa27a48: f1(5)> 4
<Greenlet at 0x2226aa27e48: f2(5)> 4
<Greenlet at 0x2226abf3048: f3(5)> 4
'''

2.當然,實際代碼裏,我們不會用gevent.sleep()去切換協程,而是在執行到IO操作時,gevent自動切換,比如:

from gevent import monkey
import gevent
import random
import time

# 有耗時操作時需要,這裏patch_all()相當於在gevent.sleep()
monkey.patch_all()  # 將程序中用到的耗時操作的代碼,換爲gevent中自己實現的模塊,實現協程切換

def ct_work(args):
    for i in range(4):
        print(args, i)
        time.sleep(random.random())

gevent.joinall([ #替換了逐個join()操作。
        gevent.spawn(ct_work, "work1"),
        gevent.spawn(ct_work, "work2")
])
''''注意,如果不加patch_all()耗時操作,這裏是順序輸出的。
work1 0
work2 0
work2 1
work1 1
work1 2
work2 2
work2 3
work1 3
'''

尖叫提示:

使用Gevent的性能確實要比用傳統的線程高,甚至高很多。但這裏不得不說它的一個坑:

  1. Monkey-patching,我們都叫猴子補丁,因爲如果使用了這個補丁,Gevent直接修改標準庫裏面大部分的阻塞式系統調用,包括socket、ssl、threading和 select等模塊,而變爲協作式運行。但是我們無法保證你在複雜的生產環境中有哪些地方使用這些標準庫會由於打了補丁而出現奇怪的問題
  2. 第三方庫支持。得確保項目中用到其他用到的網絡庫也必須使用純Python或者明確說明支持Gevent

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章