Python中多任務的實現可以使用進程和線程,也可以使用協程。
一、協程介紹
協程,又稱微線程。英文名Coroutine。協程是Python語言中所特有的,在其他語言中沒有。
協程是python中另外一種實現多任務的方式,比線程更小、佔用更小執行單元(理解爲需要的資源)。
在一個線程中的某個函數,可以在任何地方保存當前函數的一些臨時變量等信息,然後切換到另外一個函數中執行。
注意不是通過調用函數的方式做到的,並且切換的次數以及什麼時候再切換到原來的函數都由開發者自己決定。
在實現多任務時, 線程切換從系統層面遠不止保存和恢復CPU上下文那麼簡單。 操作系統爲了程序運行的高效性每個線程都有自己緩存數據,操作系統還會幫你做這些數據的恢復操作。 所以線程的切換非常耗性能。但是協程的切換隻是單純的操作CPU的上下文,所以一秒鐘切換個上百萬次系統都抗的住。
二、通過yield實現任務切換
import time
def coroutine1():
for i in range(5):
print("-----coroutine1-----")
yield
time.sleep(1)
def coroutine2():
for i in range(5):
print("-----coroutine2-----")
yield
time.sleep(1)
if __name__ == "__main__":
c1 = coroutine1()
c2 = coroutine2()
for i in range(3):
next(c1)
next(c2)
運行結果:
-----coroutine1-----
-----coroutine2-----
-----coroutine1-----
-----coroutine2-----
-----coroutine1-----
-----coroutine2-----
yield可以用來實現生成器,當函數中有yield時,則不再是函數而是生成器。
參考:https://blog.csdn.net/weixin_43790276/article/details/90523155
當運行到yield時,會保存當前運行狀態,然後暫停執行,直到使用next()方法再次調用生成器時,纔會從yield處開始執行,再次回到yield處暫停住。
利用yield的特性,我們可以用來切換任務,當任務1暫停的時候,切換到任務2,當任務2暫停時,切換回任務1,如此循環。
上面的代碼中,有兩個任務coroutine1和coroutine2,coroutine1和coroutine2中都有yield關鍵字,所以我們可以在coroutine1和coroutine2間來回切換執行。
協程就是通過yield來實現多個任務之間的切換的。
三、使用greenlet實現任務切換
安裝greenlet:
pip install greenlet
import greenlet
import time
def greenlet1():
for i in range(3):
print("-----greenlet1-----")
time.sleep(1)
g2.switch()
def greenlet2():
for i in range(3):
print("-----greenlet2-----")
time.sleep(1)
g1.switch()
g1 = greenlet.greenlet(greenlet1)
g2 = greenlet.greenlet(greenlet2)
# 切換到gr1中運行
g1.switch()
運行結果:
-----greenlet1-----
-----greenlet2-----
-----greenlet1-----
-----greenlet2-----
-----greenlet1-----
-----greenlet2-----
python中的greenlet模塊對yield切換任務的功能進行了封裝,可以通過代碼來切換任務。
上面的代碼中,我們寫了兩個函數greenlet1和greenlet2,在greenlet1中的代碼執行完成後,通過switch()切換到greenlet2,greenlet2中的代碼執行完成後,又通過switch()方法切換回greenlet1。這樣就實現了任務的切換,我們要調用兩個任務,只需要在主線程中先切換到greenlet1,程序就會在greenlet1和greenlet2之間來回切換執行。
四、使用gevent實現協程
安裝gevent:
pip install gevent
import gevent
import time
def gevent_func():
for i in range(3):
print(gevent.getcurrent(), i)
gevent.sleep(1)
start = time.time()
gevent_func()
gevent_func()
gevent_func()
end = time.time()
print('One coroutine: ', end - start)
gevent_start = time.time()
g1 = gevent.spawn(gevent_func,)
g2 = gevent.spawn(gevent_func,)
g3 = gevent.spawn(gevent_func, )
g1.join()
g2.join()
g3.join()
gevent_end = time.time()
print('Multi coroutine: ', gevent_end - gevent_start)
運行結果:
0無錫婦科醫院 http://mobile.wxbhnkyy39.com/
1
2
0
1
2
0
1
2
One coroutine: 9.023481130599976
0
0
0
1
1
1
2
2
2
Multi coroutine: 3.0032901763916016
python中的gevent模塊對greenlet模塊進行了封裝,實現了自動切換任務,實現了協程。
其原理是當一個greenlet遇到IO(input output輸入輸出)、阻塞(比如網絡延遲、文件操作等)操作時,就自動切換到其他的greenlet,等到IO操作完成,再在適當的時候切換回來繼續執行。
由於IO操作非常耗時,經常使程序處於等待狀態,有了gevent爲我們自動切換協程,就保證總有greenlet在運行,而不是等待IO。
上面的代碼中,執行三次任務,任務是在同一個協程中執行的,耗時9秒多,當我們創建三個協程來執行任務時,耗時3秒多,說明使用多協程實現了多任務。
這也是爲什麼前面使用yield和greenlet時只說是任務切換,因爲yield和greenlet是把一個任務(包含耗時等待)執行完後再切換到另一個任務,所以只起到任務切換的作用。而使用gevent時,只要遇到等待就會自動切換到其他協程,可以跳過等待的時間。
五、gevent的monkey補丁
細心的您應該已經看到,上面我們使用gevent實現協程的代碼中,不是使用time.sleep(1),而是使用的gevent.sleep(1)來模擬等待。
因爲在程序中有耗時操作時,需要將耗時操作的代碼換爲gevent中自己實現的模塊,這樣才能自動切換協程。
但是,開發中代碼比上面的例子複雜多了,我們儘量不要去修改之前的代碼。所以gevent模塊中提供了一個monkey補丁,幫我實現將耗時代碼換成gevent自己實現的模塊。
import gevent
from gevent import monkey
import time
monkey.patch_all()
def gevent_func():
for i in range(3):
print(gevent.getcurrent(), i)
time.sleep(1)
gevent_start = time.time()
g1 = gevent.spawn(gevent_func,)
g2 = gevent.spawn(gevent_func,)
g3 = gevent.spawn(gevent_func, )
g1.join()
g2.join()
g3.join()
gevent_end = time.time()
print('Multi coroutine: ', gevent_end - gevent_start)
運行結果:
0
0
0
1
1
1
2
2
2
Multi coroutine: 3.0212624073028564
通過monkey補丁,即使代碼裏面使用的是time.sleep(1),在運行程序時,monkey補丁會幫我們自動更換。
六、進程、線程、協程對比
1.進程切換需要的資源最大,效率很低。
2.線程切換需要的資源一般,效率一般,介於進程和協程之間(在不考慮GIL的情況下)。
3.協程切換任務資源很小,效率高。