1、开销小,属于程序级别切换,更加轻量
2、单线程内就可以实现并发的效果,最大限度的利用cpu
缺点:
1、本质还是单线程无法利用多核
2、协程本质还是单线程,一旦出现阻塞会阻塞整个线程
一个进程会有三种状态,阻塞、运行和就绪。就绪并不能提升效率,它是用来保存状态的。
yield就是一种在单线程下可以保存任务运行状态的方法。下面我们用yield证明,对于计算密集型的任务协程反倒会降低效率。
# 并发执行 import time def producer(): g = consumer() next(g) for i in range(1000000): g.send(i) def consumer(): while True: res = yield start_time = time.time() producer() stop_time = time.time() print(stop_time - start_time) # 0.09404683113098145 # 串行执行 import time def producer(): res = [] for i in range(1000000): res.append(i) return res def consumer(res): pass start_time = time.time() res = producer() consumer(res) stop_time = time.time() print(stop_time - start_time) # 0.07196402549743652
总结:遇到IO操作时在使用协程。
from greenlet import greenlet import time # 并不能遇到I/O才切,意义不大 def eat(name): print('%s eat 1' % name) time.sleep(10) # 遇到IO就卡住了 g2.switch('edward') # 吃一口切到play去玩 print('%s eat 2' % name) g2.switch() def play(name): print('%s paly 1' % name) g1.switch() print('%s paly 2' % name) g1 = greenlet(eat) g2 = greenlet(play) g1.switch('edward')
三、Gevent
gevent是一个第三方库,可以轻松实现并发同步或异步编程,主要模式是Greenlet。它是以C扩展模块形式接入Python的轻量级协程,全部运行在操作系统进程的内部。
用法
g1=gevent.spawn(func,1,,2,3,x=4,y=5)创建一个协程对象g1,spawn括号内第一个参数是函数名,如eat,后面可以有多个参数,可以是位置实参或关键字实参,都是传给函数eat的 g2=gevent.spawn(func2) g1.join() #等待g1结束 g2.join() #等待g2结束 #或者上述两步合作一步:gevent.joinall([g1,g2]) g1.value#拿到func1的返回值
代码示例
from gevent import monkey;monkey.patch_all() import gevent import time # 换成time.sleep()就不能切了,也就是gevent无法识别其他的I/O操作,只能实现自己的。这就需要用monkey.patch_all()来进行标识,才能用time.sleep() def eat(name): print('%s eat 1' % name) time.sleep(3) print('%s eat 2' % name) def play(name): print('%s paly 1' % name) time.sleep(4) print('%s paly 2' % name) start_time = time.time() g1 = gevent.spawn(eat, 'edward') # 只是异步提交了 g2 = gevent.spawn(play, 'alex') gevent.joinall([g1, g2]) stop_time = time.time() print(start_time - stop_time) # -4.006382703781128