1. 协程
进程是CPU资源分配的最小单位; 线程是CPU调度的最小单位;
协程就是在一个线程内切换执行任务;
之前使用yield也可以实现在一个主线程中切换执行:
def func1(): # 生成器函数 print("吃饭吗") yield print("那我们走吧") yield def func2(): g=func1() # 生成器函数在被调用时不会立即执行,除非next(g)触发才可以 next(g) # 开始执行func1()函数,但是遇到yield就会停止 print("吃") next(g) print("好的,走吧") func2()
使用yield实现生产者消费者模型;
import random import time def consumer(): # 消费者模型 while True: n=yield # yield接收g.send()的结果,然后赋值给n time.sleep(random.random()) print("消费了%s"%n) def procuder(): # 生产者模型 g=consumer() # 调用生成器函数,并不会理解执行生成器函数内部的代码(除非next()进行触发) next(g) # 开始执行生成器函数 for i in range(10): print("生产了%s"%i) g.send(i) # send()给生成器函数yield处接收 procuder()
运行结果::
2. greenlet 实现同一线程内切换
from greenlet import greenlet def func1(): print("hello,xuanxuan") g2.switch() # 切换执行func2() print("byte~ xuanxuan") g2.switch() def func2(): print("hello,xixi") g1.switch() # 切换执行func1() print("bye~") g1=greenlet(func1) g2=greenlet(func2) g1.switch() # 切换执行func1
运行结果:
另外需要注意greenlet切换时是不规避IO时间的:
from greenlet import greenlet import time def func1(): print("func1:吃饭去吗") g2.switch() time.sleep(2) # greenlet进行切换时,并不会规避掉IO时间(也就是切换回来时还是需要等待2秒在执行) print("func1:那我们走吧") g2.switch() def func2(): time.sleep(2) print("func2:吃") g1.switch() time.sleep(3) print("func2:好的") g1=greenlet(func1) g2=greenlet(func2) g1.switch()
运行结果:
如果在同一个程序有IO的情况下,才切换会让效率提高很多,但是yield greenlet均不会在切换时规避掉IO时间
其实就相当于原来func1需要等待3秒,但是如果不切换,顺序执行,那么func2也会跟着等待3秒,浪费了两个人的时间,但是如果此时func1这时切换到让func2执行,就可以不让fun2等待func1需要等待的时间,就可以某种程度上实现并发(有点牵强,但是确实有一种func1 func2交替执行,你一句我一句这种并发的效果)
3. gevent---实现协程切换
import gevent def func1(): print("吃饭去吗") gevent.sleep(2) # gevent可以在gevevt.sleep()自己认识的IO操作切换 print("那我们走吧") gevent.sleep(2) def func2(): print("吃") gevent.sleep(2) print("好的") g1=gevent.spawn(func1) g2=gevent.spawn(func2) # g1.join() # 主线程等待结束 # g2.join() gevent.joinall([g1,g2]) # 相当于上面的g1.join() g2.join()
gevent 就是当遇到gevent.sleep() IO 时会自动切换;
gevent()对普通的IO (比如time模块的sleep,socket 以及urllib request等网络请求)是无法切换的:
import gevent import time def func1(): print("func1:吃饭去吗") time.sleep(2) # gevent对普通模块的IO比如time.sleep()是无法完成切换的 print("func1:那我们走吧") time.sleep(1) def func2(): print("func2:吃") time.sleep(2) print("func2:好的") g1=gevent.spawn(func1) g2=gevent.spawn(func2) gevent.joinall([g1,g2]) # 等待所有函数执行完 因为主线程跟使用gevent 切换函数执行是完全异步的
运行结果:
那如果想在使用gevent在单线程执行函数内遇到其他模块的IO(time.sleep() socket模块的accept recv 以及urllib requests的网络请求)之间切换 可以在所有IO请求的模块上面加上一句: from gevent import monkey;monkey.patch_all() 即可实现gevent在单线程 执行函数内遇到其他模块的IO 也可以完成切换:
from gevent import monkey;monkey.patch_all() # 就可以使用gevent在单线程执行函数内遇到其他模块的IO操作时也可以完成切换 # 但是这句话必须加在其他模块的最上面(socket time等包含IO的模块) import time import gevent def func1(): print("func1:吃饭去吗") time.sleep(2) print("func1:那我们走吧") time.sleep(1) def func2(): print("func2:吃") time.sleep(2) print("func2:好的") g1=gevent.spawn(func1) g2=gevent.spawn(func2) gevent.joinall([g1,g2]) # 等待所有函数执行完 因为主线程跟使用gevent 切换函数执行是完全异步的
运行结果: