回憶進程與線程 引入
我們之前說,有比較耗時的操作得交給線程來幹,因爲進程是同步調用,阻塞執行,串行執行的,那麼我們就要用線程,異步調用,非阻塞,並行執行,這樣的話才能提升效率。
問題來了,假設線程中也有幾個子任務,耗時,然後我們程序都是串行的,也就一個Thread裏面的run()
都是串行執行的,一個子任務,比方下載很慢,其他任務都阻塞在那,就效率又很低了。
這時的線程就好像以前的進程一樣尷尬。
我們python就出來一個微線程,可以並行操作的,線程的子任務部分,也稱爲
攜程
協程(Coroutine) cor - routine
也可以看出並行的含義
比如,我們有很多任務要做,打遊戲,學習python,配朋友,這就是三個進程。在打遊戲中,我有 黑暗之魂1,2,3,上古卷軸5,也就是4個線程,當然,上古卷軸5有100個mod,我需要更新100個mod,就有了100個協程。因爲更新比較耗時,必須並行調用 更新mod的任務。
greenlet
python封裝了庫greenlet來實現協程,我們來看個例子:
#-*- utf-8 -*-
from time import sleep,time
from greenlet import greenlet
def taskA():
for i in range(5):
print('A'+str(i))
gB.switch()
sleep(1) # 模擬網絡阻塞 耗時操作
def taskB():
for i in range(5):
print('B' + str(i))
gC.switch()
sleep(1) # 模擬網絡阻塞 耗時操作
def taskC():
for i in range(5):
print('C' + str(i))
gA.switch()
sleep(1) # 模擬網絡阻塞 耗時操作
if __name__ == "__main__":
t1 = time()
gA = greenlet(run = taskA)
gB = greenlet(run = taskB)
gC = greenlet(run = taskC)
gA.switch()
t2 = time()
print("time consume",t2-t1)
類似process thread, greenlet也必須傳target參數(他命名爲run參數 反正一個意思)
這裏主要是實現A B C並行輪詢,但是我們可以看到這樣寫很蠢,需要人工切換,而且效率也不算高,於是官方的greenlet不好用,就有大佬弄出來gevent庫(第三方),在greenlet基礎上再封裝一波,可以實現自動切換。
gevent
利用spawn函數傳要做的事(taskA B C),spawn有 產卵; 引發; 引起 這幾個意思,有點run的含義。
注意,我們之前在進程裏面遇到過,主進程啓動完子進程start以後就跑路了,導致子進程也gg,這樣我們就用join阻塞,讓主進程別溜,這裏也是同樣的道理。
#-*- utf-8 -*-
from time import sleep,time
from gevent import monkey,spawn
def taskA():
for i in range(3):
print('A'+str(i))
sleep(1) # 模擬網絡阻塞 耗時操作
def taskB():
for i in range(3):
print('B' + str(i))
sleep(1) # 模擬網絡阻塞 耗時操作
def taskC():
for i in range(3):
print('C' + str(i))
sleep(1) # 模擬網絡阻塞 耗時操作
if __name__ == "__main__":
t1 = time()
gA = spawn(run = taskA)
gB = spawn(run = taskB)
gC = spawn(run = taskC)
gA.join()
gB.join()
gC.join()
t2 = time()
print("time consume",t2-t1)
結果:
A0
A1
A2
B0
B1
B2
C0
C1
C2
time consume 9.041131258010864
我們發現,
1: 並沒有輪詢ABC ABC 這樣的啊
2: 並沒有並行啊2333 其實上面的greenlet也沒有實現並行??? 還是9s
monkey
我們的time不適合這個場合的,於是利用monkey.patch_all()
#-*- utf-8 -*-
import time
from gevent import monkey,spawn
monkey.patch_all()
def taskA():
for i in range(3):
print('A'+str(i))
time.sleep(1) # 模擬網絡阻塞 耗時操作
def taskB():
for i in range(3):
print('B' + str(i))
time.sleep(1) # 模擬網絡阻塞 耗時操作
def taskC():
for i in range(3):
print('C' + str(i))
time.sleep(1) # 模擬網絡阻塞 耗時操作
if __name__ == "__main__":
t1 = time.time()
gA = spawn(run = taskA)
gB = spawn(run = taskB)
gC = spawn(run = taskC)
gA.join()
gB.join()
gC.join()
t2 = time.time()
print("time consume",t2-t1)
這裏 必須time.sleep才能生效 因爲monkey在背後偷偷重載替換了原生的sleep,你直接from time import sleep, monkey匹配不到
結果:
A0
B0
C0
A1
B1
C1
A2
B2
C2
time consume 3.036893606185913
Process finished with exit code 0
可見,比較完美實現了並行,time consume 時間消耗 從9s 並行變成了 3s
總結
我們直接用gevent monkey就行了 其他的執行方式太麻煩了,簡直就是再造輪子:)