生成器语法
yield 一个对象
返回这个对象
暂停这个函数
等待下次next重新激活
def my_gen():
print('第一次执行')
a = yield 1
print('a is:', a)
print('第二次执行')
b = yield 2
print('b is:', b)
print('第三次执行')
c = yield 3
print('c is:', c)
g = my_gen()
v1 = next(g)
print(v1)
v2 = next(g)
print(v2)
v3 = next(g)
print(v3)
运行结果:send与yield的切换
send 一个对象
激活生成器
执行生成器里面的代码
遇到yield回到调用位置
def my_gen():
print('第一次执行')
a = yield 1
print('a is:', a)
print('第二次执行')
b = yield 2
print('b is:', b)
print('第三次执行')
c = yield 3
print('c is:', c)
g = my_gen()
v1 = next(g) #相当于g.send(None) ,生成器的启动需要next或者send(None),否则报错
print(v1)
v2 = g.send('cainiao')
print(v2)
v3 = g.send('xiaobai')
print(v3)
运行结果:生产者/消费者模型
import random
import time
#消费者
def consumer():
while True:
data = yield
print('消费者拿到:', data)
#生产者
def producer(cos):
next(cos) #激活生成器对象,让他执行到yield
while True:
item = random.randint(0, 99) #随机生产一个数
print('生产者生产了:', item)
cos.send(item) #把item给消费者,切换到消费者函数
time.sleep(2)
c = consumer() #返回一个生成器对象
producer(c)
运行结果:注意事项:
- 对一个生成器必须要先next()让他执行到yield才能在send数据进去。
- 携程是在一个线程内的执行的,本质来说就是不同函数之间的切换调用。
- 如果某一个协程被阻塞了,整个线程(进程)都被阻塞。任意时刻,只有一个协程在执行。
greenlet
- 虽然CPython(标准Python)能够通过生成器来实现协程,但使用起来还并不是很方便。
- Python的一个衍生版 Stackless Python,实现了原生的协程,它更利于使用。于是,大家开始将 Stackless 中关于协程的代码,单独拿出来做成了CPython的扩展包。
这就是 greenlet 的由来,greenlet 是底层实现了原生协程的 C扩展库。
需要下载安装此模块
pip install greenlet
用greenlet重写上述代码:
from greenlet import greenlet
import random
import time
#消费者
def consumer():
while True:
item = p.switch() #切换到生产者,并等待生产者传入item
print('消费者拿到:', item)
#生产者
def producer():
while True:
item = random.randint(0, 99) #随机生产一个数
print('生产者生产了:', item)
c.switch(item) #将item传给消费者,切换到消费者函数
time.sleep(2) #每生产一个就休息一些,方便我们看效果
c = greenlet(consumer) #将一个普通的函数变成协程
p = greenlet(producer)
c.switch() #运行消费者
运行结果:greenlet作用
- 高性能的原生协程;
- 语义更加明确的显式切换;
- 直接将函数包装成协程,保持原有代码风格。
gevent协程
虽然,我们有了 基于 epoll 的回调式编程模式,但是却难以使用。即使我们可以通过配合 生成器协程 进行复杂的封装,以简化编程难度。但是仍然有一个大的问题: 封装难度大,现有代码几乎完全要重写gevent,通过封装了 libev(基于epoll) 和 greenlet 两个库。帮我们做好封装,允许我们以类似于线程的方式使用协程。以至于我们几乎不用重写原来的代码就能充分利用 epoll 和 协程 威力。
安装:pip install gevent
import gevent
from gevent import monkey
monkey.patch_socket() #打了这个补丁,下面导入的socket都会被epoll监控
import socket
def func(conn):
while True:
data = conn.recv(1024)
if data:
print('接受的数据%s'%data.decode())
conn.send(data)
else:
conn.close()
break
server = socket.socket()
server.bind(('0.0.0.0', 7001))
server.listen()
print('等待连接......')
while True:
conn, addr = server.accept()
print('{}上线'.format(addr))
gevent.spawn(func, conn) #创建一个协程
运行结果:
gevent的作用
遇到阻塞就切换到另一个协程继续执行 !
- 使用基于 epoll 的 libev 来避开阻塞;
- 使用基于 gevent 的 高效协程 来切换执行;
- 只在遇到阻塞的时候切换,没有轮需的开销,也没有线程的开销;
gevent协程通信
- 问题一:协程之间不是能通过switch通信嘛?
是的,由于 gevent 基于 greenlet,所以可以。 - 问题二:那为什么还要考虑通信问题?
因为 gevent 不需要我们使用手动切换,而是遇到阻塞就切换,因此我们不会去使用switch !
总结:
充分利用cpu多进程,用线程来做计算密集型任务,gevent来做IO密集型任务。