python3.x Day6 协程
协程:
#定义来自牛人alex博客
协程,又称微线程,纤程。英文名Coroutine。一句话说明什么是线程:协程是一种用户态的轻量级线程。
协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:
协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。
使用协程的目的:就是为了最大限度使用CPU,把IO操作解耦,提高程序运行速度
协程的好处:
无需线程上下文切换的开销
无需原子操作锁定及同步的开销
"原子操作(atomic operation)是不需要synchronized",所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序是不可以被打乱,或者切割掉只执行部分。视作整体是原子性的核心。
方便切换控制流,简化编程模型
高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。
缺点:
无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上.当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。
进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序
greenlet是封装好了的协程
gevent 是进一步封装了greenlet
greenlet还需要通过greenlet.greenlet(func)生成greenlet类实例,只能通过gr1.swich()方法手动切换方法执行。
gevent通过gevent.spawn(func,args)生成greenlet类实例,通过gevent.joinall([])装载Greenlet实例,即可启动各个方法,并实现自动的遇到IO就切换
一般在整个当前程序前需要from gevent import monkey引入monkey包,这是个补丁,里边有monkey.patch_all()方法,是明确标记所有IO操作,遇到就切换,
主要是像socket、urllib中的IO操作不会被gevent直接发现时使用这个补丁就能发现了。
gevent需要自行安装pip install gevent
- 必须在只有一个单线程里实现并发
- 修改共享数据不需加锁
- 用户程序里自己保存多个控制流的上下文栈
- 一个协程遇到IO操作自动切换到其它协程
源生的协程
import time import queue def consumer(name): print("--->starting eating baozi...") while True: new_baozi = yield print("[%s] is eating baozi %s" % (name,new_baozi)) #time.sleep(1) def producer(): r = con.__next__() r = con2.__next__() n = 0 while n < 5: n +=1 con.send(n) con2.send(n) print("\033[32;1m[producer]\033[0m is making baozi %s" %n ) if __name__ == '__main__': con = consumer("c1") con2 = consumer("c2") p = producer()
利用模块greenlet写协程,简单一些了
#用greenlet来写个简单的协程,大家猜一下运行结果 import greenlet #导入greenlet协程包 def green_func1(): #定义第一个协程用的方法 print(12) gr2.switch() #切换到协程gr2,保存现场,再度切换回来时,从这里开始 print(34) gr2.switch() #切换到协程gr2,保存现场,再度切换回来时,从这里开始 def green_func2(): #定义第二个协程用的方法 print(56) gr1.switch() #切换回协程gr1,保存现场,再度切换回来时,从这里开始 print(78) gr1=greenlet.greenlet(green_func1) #定义第一个协程,装入方法green_func1方法,并启动协程gr1 gr2=greenlet.greenlet(green_func2) #定义第二个协程,装入方法green_func2方法,并启动协程gr2 gr1.switch() #手动切换到协程gr1开始执行。因为没有执行过,所以切换时,从函数开头执行
来个自动的切换吧,这才是真正的写法:
#写个协程抓个网页看看 import gevent,time #引入协程模块 from urllib import request from gevent import monkey #引入协程模块下的monkey补丁模块 monkey.patch_all() #使用monkey模块下的patch_all()方法,作用是:在每个urllib模块的IO操作前增加标记,来明确协程切换时机 def f_get(url): #定义使用协程的方法, print("GET %s"%url) resp=request.urlopen(url) data=resp.read() print("%d bytes recevied from %s"%(len(data),url)) urls=[ "https://www.baidu.com/", "https://github.com/", "https://hub.docker.com/", "https://www.yahoo.com/" ] print("同步获取:") ss_time=time.time() for u in urls: f_get(u) print("同步耗时:%s"%(time.time()-ss_time)) print("协程异步获取:") async_time=time.time() gevent.joinall([ #定义协程的启动,需要传入一个gevent.spawn(其实就是Greenlet类的实例)列表 gevent.spawn(f_get,"https://www.baidu.com/"), gevent.spawn(f_get,"https://github.com/"), gevent.spawn(f_get,"https://hub.docker.com/"), gevent.spawn(f_get,"https://www.yahoo.com/") ]) print("协程异步耗时:%s"%(time.time()-async_time)) print("循环生成Greenlet实例变成列表协程异步获取:") gl=[] for uu in urls: gl.append(gevent.spawn(f_get,uu)) async_time=time.time() gevent.joinall(gl)#定义协程的启动,需要传入一个gevent.spawn(其实就是Greenlet类的实例)列表 print("协程异步耗时:%s"%(time.time()-async_time))