#协程的概念
#模块操做协程
# 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框架 # 题高网络编程的效率和并发效果