1.协程
为什么要使用协程
- 协程最大的优势就是协程极高的执行效率。子程序切换由程序自身控制,因此,没有线程切换的开销
- 第二大优势就是不需要多线程的锁机制,阻塞自动切换
协程即是生成器对象的yield和send方法的配合使用
生成器语法
yield 一个对象
- 返回这个对象
- 暂停这个函数
- 能带下次next重新激活
def func():
print('第一次执行')
yield 1 #返回1,并暂停函数
print('第二次执行')
yield 2 #返回2,并暂停函数
print('第三次执行')
#没有代码了,引发异常
f = func()
v1 = next(f) #直至第一个yield
print(v1) #输出第一个yield的值
v2 = next(f) #直至第二个yield
print(v2) #输出第三个yield的值
v3 = next(f) #输出至下一个yield,但并没有下一个yield,抛出StopItertion异常
第一次执行
1
第二次执行
2
第三次执行
Traceback (most recent call last):
File "/home/pyvip/py_case/网络编程/生成器.py", line 13, in <module>
v3 = next(f) #输出至下一个yield,但并没有下一个yield,抛出StopItertion异常
StopIteration
send与yield的切换
send一个对象
- 激活生成器
- 更改yield的返回值
- 执行生成器里面的代码
- 遇到yield回到调用位置
def fun():
i = 0
while True:
x = yield i
i += 1
print('第%d次执行函数'%(i))
print('x的值为',x)
f = fun()
next(f) #相当于f.send(Nnone) yield 0 暂停
f.send('hello') #发送,使x的值为hello 继续执行 print print yield 2暂停
print('--'*10)
f.send('python') #发送,使x的值为python 继续执行 print print yield 3暂停
第1次执行函数
x的值为 hello
--------------------
第2次执行函数
x的值为 python
生成器生产者消费者版本(生产一个消费一个)
import random
import time
def produce(consumer):
next(consumer)
while True:
item = random.randint(0,99)
print('生产者生产了:',item)
consumer.send(item)
time.sleep(2)
def consumer():
while True:
item = yield
print('消费者消费了:',item)
#先运行producer函数,next(consumer)激活consumer函数。执行item = yield暂停
#返回producer,生产一个资源,send回consumer,consumer的item接收并输出
c = consumer()
produce(c)
生产者生产了: 9
消费者消费了: 9
生产者生产了: 28
消费者消费了: 28
生产者生产了: 19
消费者消费了: 19
生产者生产了: 8
消费者消费了: 8
注意事项
- 携程是在一个线程内的执行的,本质来说就是不同函数之间的切换调用。
- 对一个生成器必须要先next()让他执行到yield才能在send数据进去。
- 如果某一个协程被阻塞了,整个线程(进程)都被阻塞。任意时刻,只有一个协程在执行。
2.greenlet协程
由来
虽然CPython(标准Python)能够通过生成器来实现协程,
但使用起来还并不是很方便。 与此同时,Python的一个衍生版 Stackless Python
实现了原生的协程,它更利于使用。 于是,大家开始将 Stackless 中关于协程的代码
单独拿出来做成了CPython的扩展包。 这就是 greenlet 的由来,因此 greenlet 是底层实现了原生协程的 C扩展库。
基本使用
import greenlet 导入
c = greenlet.greenlet() 创建协程
c.switch() 运行c协程
import greenlet
import random
def produce():
while True:
item = random.randint(0,99)
print("produce ",item)
c.switch(item) #将data传给c,并切换到c
def consume():
while True:
item = p.switch() #切换到p,等待传入数值
print("consume ",item)
c= greenlet.greenlet(consume) #将普通函数函数变成协程
p= greenlet.greenlet(produce)
c.switch() #运行p(消费者)协程 谁switch就运行谁
produce 6
consume 6
produce 63
consume 63
produce 1
consume 1
greenlet 的价值
- 价值一: 高性能的原生协程
- 价值二: 语义更加明确的显式切换
- 价值三: 直接将函数包装成协程,保持原有代码风格
3.gevent协程
gevent是什么
虽然,我们有了 基于 epoll 的回调式编程模式,但是却难以使用。
即使我们可以通过配合 生成器协程 进行复杂的封装,以简化编程难度。
但是仍然有一个大的问题: 封装难度大,现有代码几乎完全要重写 。
gevent通过封装了 libev(基于epoll) 和 greenlet 两个库。
帮我们做好封装,允许我们以类似于线程的方式使用协程。
以至于我们几乎不用重写原来的代码就能充分利用 epoll 和 协程 威力。
gevent的价值
- 价值一: 使用基于 epoll 的 libev 来避开阻塞
- 价值二: 使用基于 gevent 的 高效协程 来切换执行。遇到阻塞就切换到另一个协程继续运行
- 价值三: 只在遇到阻塞的时候切换, 没有轮需的开销,也没有线程的开销
genent并发服务器
import genent 导入
gevent.spawn(参数) 创建协程
服务端
from gevent import monkey;monkey.patch_socket()
import gevent
import socket
sock= socket.socket()
sock.bind(('',8088))
sock.listen(10)
def worker(con):
while True:
try:
data = con.recv(1024)
if data:
print(data)
con.send(data)
else:
con.close()
break
except Exception as e:
print(e)
break
while True:
con,addr=sock.accept()
gevent.spawn(worker,con) #创建协程并传入参数con
客户端
import socket
client = socket.socket()
client.connect(('127.0.0.1',8088))
while True:
data = input('please write the message:')
client.send(data.encode())
print(client.recv(1024))
if data == 'Q' or data == 'q':
break
client.close()
4.gevent通信
问题引入
问: 协程之间不是能通过switch通信嘛?
答:是的,由于 gevent 基于 greenlet,所以可以。
问: 那为什么还要考虑通信问题?
答:因为 gevent 不需要我们使用手动切换, 而是遇到阻塞就切换,因此我们不会去使用switch !
gevent生产者消费者
gevent.joinall( [ 协程列表] ) gevent协程运行列表
from gevent.queue import Queue 使用gevent队列
import gevent
from gevent.queue import Queue
import random
def Consume(queue):
while True:
item=queue.get()
print("消费者,消费:%s"%item)
def Produce(queue):
while True:
item = random.randint(0,99)
queue.put(item)
print("生产者,生产:%s"%item)
queue = Queue(3)
p=gevent.spawn(Produce,queue)
c=gevent.spawn(Consume,queue)
gevent.joinall([p,c])
生产者,生产:58
生产者,生产:43
生产者,生产:17
消费者,消费:58
消费者,消费:43
消费者,消费:17
生产者,生产:8
生产者,生产:56
生产者,生产:65