协程、greenlet 协程与gevent 协程

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

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值