#協程的概念
#模塊操做協程
# gevent 擴展模塊
# asyncio 內置模塊
# 基礎的語法html
[1]python
import time def func1(): print(1) yield 1 time.sleep(1) print(2) def func2(): g=func1() next(g) func2() ------------結果: 1
[2]web
import time def func1(): print(1) yield 1 time.sleep(1) print(2) yield def func2(): g=func1() next(g) print('func2') next(g) func2() ------------結果: 1 func2 2
#註釋:執行函數2的過程當中實現了兩次切換,可是阻塞sleep沒有規避掉編程
import time def func1(): print(1) yield 1 time.sleep(1) print(2) g=func1() next(g) print('func2') next(g) ---------結果: 1 func2 2 ...... next(g) StopIteration
#註釋:跟2相比,執行生成器最後的print(2),因爲後面沒有yield了,雖然語句執行了可是會報錯。全部生成器最後應該是yield,這樣取最後的值纔不會出現報錯。超出迭代的數了就會報錯,一個yield一次迭代。
[4]小結:
#協程 :可以在一個線程下的多個任務之間來回切換,那麼每個任務都是一個協程
實現一個線程多個個任務的切換就是協程了,可是隻是yield還不能規避io實現協程切換。
原生python的asyncio就是靠yield實現協程的網絡
Switch在一些計算機語言中是保留字,其做用大多狀況下是進行判斷選擇。以C語言來講,switch(開關語句)常和case break default一塊兒使用。併發
#實例化一個greenlet(eat)對象,將函數傳進去,對象.開關()執行這個函數app
import time from greenlet import greenlet def eat(): print('魔降風雲變 is eating') time.sleep(0.5) print('魔降風雲變 finished eat') def sleep(): print('小馬過河 is sleeping') time.sleep(0.5) print('小馬過河 finished sleep') g1 = greenlet(eat) g1.switch() -----------結果: 魔降風雲變 is eating 魔降風雲變 finished eat
#g1.switch()執行g1的任務函數,g1的任務函數中遇到g2.switch()就去執行g2的任務函數。此時g2執行完了,可是g1沒有繼續往下執行了。框架
import time from greenlet import greenlet def eat(): print('魔降風雲變 is eating') g2.switch() time.sleep(0.5) print('魔降風雲變 finished eat') def sleep(): print('小馬過河 is sleeping') time.sleep(0.5) print('小馬過河 finished sleep') g1 = greenlet(eat) g2 = greenlet(sleep) g1.switch() --------------結果: 魔降風雲變 is eating 小馬過河 is sleeping 小馬過河 finished sleep
#g1.switch()執行g1任務函數eat,eat中遇到g2.switch()執行g2任務函數sleep,sleep執行完後再執行g1.switch(),即回到eat函數繼續上一次的位置往下執行。這樣保證了兩個函數都執行完且中間執行過程當中作了切換。異步
import time from greenlet import greenlet def eat(): print('魔降風雲變 is eating') g2.switch() time.sleep(0.5) print('魔降風雲變 finished eat') def sleep(): print('小馬過河 is sleeping') time.sleep(0.5) print('小馬過河 finished sleep') g1.switch() g1 = greenlet(eat) g2 = greenlet(sleep) g1.switch() ------------結果: 魔降風雲變 is eating 小馬過河 is sleeping 小馬過河 finished sleep 魔降風雲變 finished eat
以前的sleep是不能實現規避io的,須要本身寫一個去實現規避io。
[1]async
import time def sleep(num): t=num+time.time() yield t sleep(2) --------結果: 沒任何反應
import time def sleep(num): t=num+time.time() yield t g=sleep(2) #執行這個函數的意思是計算2秒以後的時間戳是多少 print(g,type(g)) for i in g: #t是個生成器,for循環取值 print(i) --------------結果: <generator object sleep at 0x005C3300> <class 'generator'> 1558265393.7168157
import time def sleep(num): t=num+time.time() yield t g=sleep(2) print(next(g)) ---------結果: 1558265587.9589255
import time def sleep(num): t=num+time.time() yield t g=sleep(2) t=next(g) print(t) -----------結果: 1558265787.9023616
import time def sleep(num): t=num+time.time() yield t g1=sleep(1) g2=sleep(2) t1=next(g1) t2=next(g2) print(t1) print(t2) -----------結果: 1558265947.809565 1558265948.809565
import time def sleep(num): t=num+time.time() yield t g1=sleep(1) g2=sleep(2) t1=next(g1) t2=next(g2) li=[t1,t2] min_t=min(li) print(min_t) ------------結果: 1558266080.370147
import time def sleep(num): t=num+time.time() yield t print('sleep結束!') yield g1=sleep(1) g2=sleep(2) t1=next(g1) t2=next(g2) li=[t1,t2] min_t=min(li) print('經過新的sleep函數執行阻塞多長時間以後的全部時間戳li1:',li) print("li1中阻塞時間中離得最近的將來時間:",min_t,' 當前時間:',time.time()) li.remove(min_t) g1zusetime=min_t-time.time() print('第一次應阻塞時間長(計算最近的-當前的時間):%s-%s=%s'%(min_t,time.time(),g1zusetime)) print() print('''---------- 將列表中離得最近的將來時間戳刪掉,而後用python自帶的sleep方法睡將來時間到如今時間的時間差。 ----------''') print() time.sleep(g1zusetime) min_t=min(li) g2zusetime=min_t-time.time() print('li2(減去上一次最小值的列表)中阻塞時間列表:',li) print("li2中阻塞時間中離得最近的將來時間:",min(li),"執行阻塞第一次應阻塞時間長以後的當前時間:",time.time()) print('第二次應阻塞時間長(計算最近的-當前的時間):%s-%s=%s'%(min_t,time.time(),g1zusetime)) -------------------------結果: 經過新的sleep函數執行阻塞多長時間以後的全部時間戳li1: [1558268892.4589894, 1558268893.4589894] li1中阻塞時間中離得最近的將來時間: 1558268892.4589894 當前時間: 1558268891.4589894 第一次應阻塞時間長(計算最近的-當前的時間):1558268892.4589894-1558268891.4589894=1.0 ---------- 將列表中離得最近的將來時間戳刪掉,而後用python自帶的sleep方法睡將來時間到如今時間的時間差。 li2(減去上一次最小值的列表)中阻塞時間列表: [1558268893.4589894] li2中阻塞時間中離得最近的將來時間: 1558268893.4589894 執行阻塞第一次應阻塞時間長以後的當前時間: 1558268892.4590466 第二次應阻塞時間長(計算最近的-當前的時間):1558268893.4589894-1558268892.4590466=1.0
[8]本身寫的sleep兩次阻塞之和3秒時間(實際阻塞2秒,併發,)和Python原生兩次阻塞時間之和爲3秒(時間阻塞3秒,串行)的實際阻塞時間對比
#本身寫的是將兩次阻塞中都要阻塞相同時間的地方只阻塞一次,相似併發。Python原生兩次阻塞是阻塞一次,再阻塞一次,時間片沒有重疊的部分。
1)
import time def sleep(num,g): t=num+time.time() yield t print('%ssleep結束!'%g) yield start=time.time() g1=sleep(1,"g1") g2=sleep(2,"g2") t1=next(g1) t2=next(g2) li=[t1,t2] min_t=min(li) li.remove(min_t) g1zusetime=min_t-time.time() time.sleep(g1zusetime) g1.__next__() min_t=min(li) g2zusetime=min_t-time.time() time.sleep(g2zusetime) g2.__next__() print("實現併發效果:",time.time()-start) -----------結果: g1sleep結束! g2sleep結束! 實現併發效果: 2.0001144409179688
2)
import time start=time.time() time.sleep(1) time.sleep(2) print("python原生sleep方法執行時間:",time.time()-start) ----------結果; python原生sleep方法執行時間: 3.000171661376953
#注意:
1)把sleep當成阻塞,sleep多是io也多是在作別的事情,記錄何時切換回去執行。按照協程的原理去思考。好比網絡編程那裏recv它能計算到大概何時
2)這個執行next和阻塞時間的計算是第三者幫你作的,靠第三者(不是任務,是調度任務的)來調度的。全部的事情提交給第三者,第三者幫你調度,循環看那些任務遇到阻塞,那些任務阻塞結束,而後進行調度安排切換,執行等等。sleep1 2 對於第三者來講都是一個個事件
事件循環的概念:第三者一直在循環全部的任務調度全部的任務
#gevent.產卵(任務函數)建立協程任務,就已經開始執行了
import time import gevent def eat(): print('魔降風雲變 is eating') time.sleep(1) print('魔降風雲變 finished eat') def sleep(): print('小馬過河 is sleeping') time.sleep(1) print('小馬過河 finished sleep') g1 = gevent.spawn(eat) # 創造一個協程任務 -----------結果: 沒有輸出 #協程遇到阻塞才切換,這裏代碼從上到下執行結束,沒有遇到阻塞
[2]
import time import gevent def eat(): print('魔降風雲變 is eating') time.sleep(1) print('魔降風雲變 finished eat') def sleep(): print('小馬過河 is sleeping') time.sleep(1) print('小馬過河 finished sleep') g1 = gevent.spawn(eat) # 創造一個協程任務 gevent.sleep(2) #加個gevent.sleep(2)就切換到eat執行裏面的代碼了 ---------結果: 魔降風雲變 is eating 魔降風雲變 finished eat #加個gevent.sleep(2)就切換到eat執行裏面的代碼了。eat中間time.sleep(1)照樣睡。
[3]
import time import gevent def eat(): print('魔降風雲變 is eating') time.sleep(1) print('魔降風雲變 finished eat') def sleep(): print('小馬過河 is sleeping') time.sleep(1) print('小馬過河 finished sleep') g1 = gevent.spawn(eat) # 創造一個協程任務 gevent.sleep(2) print('hahahahha') ------------結果: 魔降風雲變 is eating 魔降風雲變 finished eat hahahahha #代碼分析:代碼從上往下執行,遇到gevent.sleep(2)阻塞了發現還有一個任務,就切到eat函數任務執行,打印'魔降風雲變 is eating'以後,這個任務time.sleep(1)1秒,gevent.sleep(2)也睡完一秒了,time.sleep(1)以後打印'魔降風雲變 finished eat',任務結束以後切回到這裏gevent.sleep(2)又睡了另外一秒,而後打印出'hahahahha'。這裏總共睡了2秒,而不是3秒。兩個任務實現併發 [4]我將eat裏的 time.sleep(1)改成 time.sleep(3) #預計打印'魔降風雲變 is eating',遇到阻塞應該time.sleep(3)應該回到gevent.sleep(2)先打印'hahahahha',再打印'魔降風雲變 finished eat',結果不是的。緣由是:time.sleep(3)不是gevent的方法,它不認識的它不切換。那麼我是否是要將time.sleep(3)改成gevent.sleep(3)就實現預計的結果呢?看[5] import time import gevent def eat(): print('魔降風雲變 is eating') time.sleep(3) print('魔降風雲變 finished eat') def sleep(): print('小馬過河 is sleeping') time.sleep(1) print('小馬過河 finished sleep') g1 = gevent.spawn(eat) # 創造一個協程任務 gevent.sleep(2) print('hahahahha') ------------結果: 魔降風雲變 is eating 魔降風雲變 finished eat hahahahha
[5]
#將time.sleep(3)改成gevent.sleep(3)也沒有實現預計的結果。
緣由:
1)執行代碼g1 = gevent.spawn(eat),遇到gevent.sleep(2)阻塞切到g1任務執行eat函數print('魔降風雲變 is eating')
2)遇到gevent.sleep(3)阻塞是會切換到gevent.sleep(2)這邊的,由於2秒尚未過因此等待2秒後print('hahahahha')
3)由於print('hahahahha')代碼已經結束沒有遇到阻塞,因此沒有切回到eat函數執行,函數eat沒有執行完程序就結束了。這也不不符合咱們的預期效果
import gevent
def eat():
print('魔降風雲變 is eating')
gevent.sleep(3)
print('魔降風雲變 finished eat')
g1 = gevent.spawn(eat) # 創造一個協程任務
gevent.sleep(2)
print('hahahahha')
-------------結果:
魔降風雲變 is eating
hahahahha
[6]由[5]可知,gevent.sleep(2)沒有阻塞到切換回去執行eat函數,因此有個協程對象.join(), 阻塞 直到這個協程任務完成爲止。
import time
import gevent
def eat():
print('魔降風雲變 is eating')
gevent.sleep(3)
print('魔降風雲變 finished eat')
g1 = gevent.spawn(eat) # 創造一個協程任務
g1.join() # 阻塞 直到g1任務完成爲止
[7]符合預期效果的協程任務演示
1)建立的g1任務,先執行g1任務,遇到sleep(3)切走到g2任務,g2遇到sleep(1),g2先睡完,而後執行g2任務print('小馬過河 finished sleep'),g2任務結束。由於g1.join(),g1任務尚未結束,繼續等待。以前執行g2的時候一塊兒睡了1秒了,g2執行完後,g1還須要睡2秒,而後print('魔降風雲變 finished eat'),g1任務也結束。
2)這裏由於g2先結束了,在此處g2.join()也能夠省略掉。
3)總結:協程任務建立:gevent.產卵(任務函數)
協程任務實現調度:協程對象.join()
import time
import gevent
def eat():
print('魔降風雲變 is eating')
gevent.sleep(3)
print('魔降風雲變 finished eat')
def sleep():
print('小馬過河 is sleeping')
time.sleep(1)
print('小馬過河 finished sleep')
g1 = gevent.spawn(eat) # 創造一個協程任務
g2 = gevent.spawn(sleep) # 創造一個協程任務
g1.join() # 阻塞 直到g1任務完成爲止
g2.join()
#任務函數中time.sleep(3) gevent不認識,改爲gevent.sleep(3)。咱們用time.sleep(3),可是想要用time.sleep(3),那麼就能夠從gevnet導入猴子,並執行猴子.patch_all(),這樣之後,gevent才能識別出time.sleep(3)。其實是執行了time.sleep(3)以後,在任務函數中遇到time.sleep(1)就會將time.sleep(1)重寫一遍。即執行猴子.patch_all()以後,會在任務函數中將與它自己擁有的方法同名,那就會被重寫
import time
import gevent
from gevent import monkey
monkey.patch_all()
def eat():
print('魔降風雲變 is eating')
time.sleep(1)
print('魔降風雲變 finished eat')
def sleep():
print('小馬過河 is sleeping')
time.sleep(1)
print('小馬過河 finished sleep')
g1 = gevent.spawn(eat) # 創造一個協程任務
g2 = gevent.spawn(sleep) # 創造一個協程任務
g1.join() # 阻塞 直到g1任務完成爲止
g2.join()
-----------結果:
魔降風雲變 is eating
小馬過河 is sleeping
魔降風雲變 finished eat
小馬過河 finished sleep
[9]monkey.patch_all()做用,重寫方法。斷定是否識別出io操做
import time
from gevent import monkey
print('執行以前',time.sleep)
monkey.patch_all()
print('執行以後',time.sleep)
------------結果:
執行以後
執行以前: <function sleep at 0x022FDDB0>
[9]gevent.joinall([g1,g2]).g實際.阻塞全部傳參協程任務列表
import time
import gevent
from gevent import monkey
monkey.patch_all()
def eat():
print('魔降風雲變 is eating')
time.sleep(1)
print('魔降風雲變 finished eat')
def sleep():
print('小馬過河 is sleeping')
time.sleep(1)
print('小馬過河 finished sleep')
g1 = gevent.spawn(eat) # 創造一個協程任務
g2 = gevent.spawn(sleep) # 創造一個協程任務
gevent.joinall([g1,g2])
-----------結果:
魔降風雲變 is eating
小馬過河 is sleeping
魔降風雲變 finished eat
小馬過河 finished sleep
#代碼分析:
1)主程序從上到下執行,沒有遇到阻塞就沒有必要切了。
2)當我遇到gevent.joinall([g1,g2])阻塞,看兩個任務還在不在執行,這時看g1進入阻塞了,又看到g2遇到阻塞了,它又回到gevent.joinall([g1,g2])這個阻塞,因而程序就來回在g1和g2之間互相切換,這兩個任務誰先結束阻塞就先執行哪一個任務。等到這兩個任務結束了,gevent.joinall([g1,g2])阻塞也結束了。
[10]批量建立多個協程
import time
import gevent
from gevent import monkey
monkey.patch_all()
def eat():
print('魔降風雲變 is eating')
time.sleep(1)
print('魔降風雲變 finished eat')
g_li=[]
for i in range(10):
g=gevent.spawn(eat())
g_li.append(g)
for g in g_li:g.join()
[10]協程獲取返回值:協程對象.value屬性
1)沒有執行完協程任務就拿返回值#拿到的是默認返回值
import time
import gevent
from gevent import monkey
monkey.patch_all()
def eat():
print('魔降風雲變 is eating')
time.sleep(1)
print('魔降風雲變 finished eat')
return "魔降風雲變"
def sleep():
print('小馬過河 is sleeping')
time.sleep(1)
print('小馬過河 finished sleep')
return "小馬過河"
g1=gevent.spawn(eat)
g2=gevent.spawn(sleep)
print(g1.value,g2.value)
--------結果:
None None
#緣由:函數沒有執行,等協程任務執行完須要加阻塞
import time
import gevent
from gevent import monkey
monkey.patch_all()
def eat():
print('魔降風雲變 is eating')
time.sleep(1)
print('魔降風雲變 finished eat')
return "魔降風雲變"
def sleep():
print('小馬過河 is sleeping')
time.sleep(1)
print('小馬過河 finished sleep')
return "小馬過河"
g1=gevent.spawn(eat)
g2=gevent.spawn(sleep)
gevent.joinall([g1,g2])
print(g1.value,g2.value)
-----------結果:
魔降風雲變 is eating
小馬過河 is sleeping
魔降風雲變 finished eat
小馬過河 finished sleep
魔降風雲變 小馬過河
import asyncio
async def demo(): # 協程方法
print('start')
await asyncio.sleep(1) # 阻塞
print('end')
loop = asyncio.get_event_loop() # 建立一個事件循環
loop.run_until_complete(demo()) # 把demo任務丟到事件循環中去執行
-----------結果:
start
end
#定義一個任務:
async 在定義函數前,函數中的阻塞前面加await,阻塞事件用asyncio的方法,好比asyncio.sleep(1)
#asyncio在作協程的切換時只認本身實現的方法 #await後面跟的是協程函數和方法,是一個阻塞事件。await這個關鍵字的使用須要在使用它的函數前面添加async關鍵字標識它是一個協程函數
##這個協程函數不能本身去調用,而是asyncio的方法去調用
#調用方法:
建立對象:模塊.獲取事件循環()
對象.運行直到完成(協程執行函數())#注意,這裏須要執行
注意:不能對象.運行直到完成作多個任務,這樣就不能實現協程的併發執行
import asyncio
async def demo(): # 協程方法
print('start')
await asyncio.sleep(1) # 阻塞
print('end')
loop = asyncio.get_event_loop() # 建立一個事件循環
wait_obj = asyncio.wait([demo(),demo(),demo()])
loop.run_until_complete(wait_obj)
-------------結果;
start
start
start
end
end
end
啓動多個任務:
建立協程事件循環對象對象:模塊.獲取事件循環()
建立任務對象:模塊.等待(協程函數列表)
運行:事件循環對象.運行直到完成(任務對象)
import asyncio
async def demo(): # 協程方法
print('start')
await asyncio.sleep(1) # 阻塞
print('end')
return 123
loop = asyncio.get_event_loop()
t1 = loop.create_task(demo())
t2 = loop.create_task(demo())
tasks = [t1,t2]
wait_obj = asyncio.wait([t1,t2])
loop.run_until_complete(wait_obj)
for t in tasks:
print(t.result())
----------------結果:
start
start
end
end
123
123
啓動多個任務而且有返回值:
建立協程事件循環對象對象:模塊.獲取事件循環()
建立多個協程任務:事件循環對象.建立任務(函數),變量接收以供使用
建立任務對象:模塊.等待(協程任務列表)
運行:事件循環對象.運行直到完成(任務對象)
打印每一個協程任務的返回值:for循環每一個協程任務,而後打印任務.結果(),這裏打印有阻塞,是同步的,想異步即誰先執行完先打印誰用await打印
import asyncio
async def demo(): # 協程方法
print('start')
await asyncio.sleep(1) # 阻塞
print('end')
return 123
async def main():
task_l = []
for i in range(3):
task = asyncio.ensure_future(demo())
task_l.append(task)
for ret in asyncio.as_completed(task_l):
res = await ret
print(res)
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
--------------結果:
start
start
start
end
end
end
123
123
123
啓動多個任務而且有返回值,哪一個返回值先到先處理哪一個返回值,
建立協程事件循環對象對象:模塊.獲取事件循環()
再建立一個對返回值作處理的協程函數,函數裏for循環建立任務:模塊.確認將來(要運行的協程函數),每一個任務追加到列表,對列表進行模塊.完成的(這個列表)循環取值,每一個值用await解決阻塞。返回值誰先加到列表就先打印誰。
若是不是這樣,那麼for循環列表返回值,由於列表前面的任務沒有執行完,沒有返回值,那麼就會等待前面的那個執行完打印了它的返回值才能繼續往下打印,這樣後面的協程先執行完的就只能由於前面的協程沒有執行完而處於等待的狀態。
事件循環對象.運行直到完成(處理返回值的函數)
實現原理簡化:
asyncio.ensure_future(demo())執行
import asyncio
async def demo(): # 協程方法
await asyncio.sleep(1) # 阻塞
return 123
async def main():
task_l = []
for i in range(3):
task = asyncio.ensure_future(demo())
print('task',task)
task_l.append(task)
bb=asyncio.as_completed(task_l)
print('bb:',bb)
for ret in bb:
print('ret',ret)
res = await ret
print('res',res)
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
-----------結果:
task <Task pending coro=<demo() running at C:/mcw/study/test.py:2>>
task <Task pending coro=<demo() running at C:/mcw/study/test.py:2>>
task <Task pending coro=<demo() running at C:/mcw/study/test.py:2>>
bb: <generator object as_completed at 0x02AC1660>
ret <generator object as_completed.
res 123
ret <generator object as_completed.
res 123
ret <generator object as_completed.
res 123
import asyncio
async def demo(i): # 協程方法
print('start')
await asyncio.sleep(10-i) # 阻塞
print('end')
return i,123
async def main():
task_l = []
for i in range(4):
task = asyncio.ensure_future(demo(i))
task_l.append(task)
for ret in asyncio.as_completed(task_l):
res = await ret
print(res)
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
------------結果:
start
start
start
start
end
(3, 123)
end
(2, 123)
end
(1, 123)
end
(0, 123)
驗證原理:
task_l列表裏面for循環建立並執行任務,而後追加到列表裏的都是有序的任務。將第i次建立的任務傳進任務函數做爲任務函數的返回值,正常for循環打印task_l裏的結果是有序的,如今無序打印,全部回值誰先執行完先打印誰的
#asyncio小結:
# await 阻塞 協程函數這裏要切換出去,還能保證一下子再切回來 # await 必須寫在async函數裏,async函數是協程函數 # loop 事件循環 # 全部的協程的執行 調度 都離不開這個loop
import asyncio
async def get_url():
reader,writer = await asyncio.open_connection('www.baidu.com',80)
writer.write(b'GET / HTTP/1.1\r\nHOST:www.baidu.com\r\nConnection:close\r\n\r\n')
all_lines = []
async for line in reader:
data = line.decode()
all_lines.append(data)
html = '\n'.join(all_lines)
return html
async def main():
tasks = []
for url in range(20):
tasks.append(asyncio.ensure_future(get_url()))
for res in asyncio.as_completed(tasks):
result = await res
print(result)
if name == 'main':
loop = asyncio.get_event_loop()
loop.run_until_complete(main()) # 處理一個任務
-----------------結果:
HTTP/1.1 200 OK
Accept-Ranges: bytes
...........
# 爬蟲 webserver框架 # 題高網絡編程的效率和併發效果