并发编程--greenlet与gevent

什么是greenlet?

虽然CPython(标准Python)能够通过生成器来实现协程,但使用起来还并不是很方便。

与此同时,Python的一个衍生版 Stackless Python实现了原生的协程,它更利于使用。

于是,大家开始将 Stackless 中关于协程的代码单独拿出来做成了CPython的扩展包。

这就是 greenlet 的由来,因此 greenlet 是底层实现了原生协程的 C扩展库

greenlet的基本使用

# 基于greenlet的生产者消费者协程
from greenlet import greenlet
import random
import time

def Producer():
    while True:
        item = random.randint(1, 10)
        print("生产<{}>中...".format(item))
        time.sleep(1)
        c.switch(item)  # 切换到消费者,并将item传入。


def Consumer():
    while True:
        item = p.switch()  # 切换到生产者。等待生产者传递参数item
        print("消费<{}>中..".format(item))

c = greenlet(Consumer)  # 将普通函数编程协程
p = greenlet(Producer)  # 同理
c.switch()  # 启动协程,Consumer先执行
"""
从consumer开始执行,执行到item=p.switch()时,程序切换到producer,并等待传参
producer得到执行权后,生成一个item,并往下执行代码
当producer执行到c.switch(item)时,程序携带传递的item切换到consumer,
consumer继续往下执行,直到下一次运行到p.switch时,交出执行权,切换到producer,重复以上过程


greenlet的价值在于高性能的原生协程,
且语义更加明确、显示切换
执行到switch时就切换程序
直接将函数包装成协程,可以保留原代码的风格
"""

什么是gevent?

虽然,我们有了 基于 epoll 的回调式编程模式,但是却难以使用。即使我们可以通过配合 生成器协程 进行复杂的封装,以简化编程难度。
但是仍然有一个大的问题: 封装难度大,现有代码几乎完全要重写gevent,通过封装了 libev(基于epoll) 和 greenlet 两个库。
帮我们做好封装,允许我们以类似于线程的方式使用协程。以至于我们几乎不用重写原来的代码就能充分利用 epoll 和 协程 威力。

gevent的常用操作

"""
gevent: 通过greenlet实现协程,核心就是遇到IO操作,会自动切换到其他协程

"""
# 将python标准库中的一些阻塞操作变为非阻塞
from gevent import monkey;monkey.patch_all()
# 使用猴子补丁要写在第一行
import gevent

def test1():
    print("test1")
    gevent.sleep(0)  # 模拟耗时操作
    print("test11")

def test2():
    print("test2")
    gevent.sleep(0)  # 模拟耗时操作
    print("test22")

# g1 = gevent.spawn(test1)  # 将函数封装成协程,并启动
# g2 = gevent.spawn(test2)
# gevent.joinall([g1, g2])

"""
# joinall() 阻塞当前流程,执行给定的greenlet(列表中的对象),等待程序执行完
# spawn是启动协程,参数为函数名及其参数

运行结果:
test1
test2
test11
test22

代码执行test1,打印test1,遇到gevent.sleep(0)时切换程序,执行test2
test()执行,打印test2,执行到gevent.sleep(0)时切换程序
执行test1在gevent.sleep(0)后面的代码,直到再次遇到gevent时,切换程序
然后在test2中,继续执行gevent后的代码,直到遇到gevent时,再次切换
直到程序执行完毕

gevent的价值在于它的使用基于epoll的libev来避开阻塞;
使用基于gevent的高效协程,来切换执行
只在遇到阻塞的时候切换,没有轮询和线程开销
"""

基于gevent的并发服务器

# 基于gevent的并发服务器实现
import gevent
# 将python内置的socket换成封装了IO多路复用的socket
from  gevent import monkey;monkey.patch_all()
import socket

# 实例化socket
server = socket.socket()
# 绑定ip和端口
server.bind(('0.0.0.0', 8000))
# 绑定监听数量
server.listen(1000)

def worker(connection):
    """
    协程需要处理的事情
    :param connection:
    :return:
    """
    while True:
        recv_data = connection.recv(1024)  # 等待接收数据
        if recv_data:
            print(recv_data)
            connection.send(recv_data)  # 将接收的数据原路返回
        else:
            connection.close()  # 发送完毕断开
            break

while True:
    conn, addr = server.accept()  # 等待客户端连接,遇到阻塞切换
    gevent.spawn(worker, conn)  # 生成协程,并将conn作为参数传入

gevent的通信

# gevent通信
import time
import random

from gevent import monkey;monkey.patch_all()
import gevent
from gevent.queue import Queue

def write(q):
    while True:
        print("put:{}".format('text'))
        q.put('text')
        gevent.sleep(0)  # 模拟阻塞

def read(q):
    while True:
        print("get:{}".format(q.get()))  # get本身是阻塞

# q = Queue()
# w = gevent.spawn(write, q)  # 遇到阻塞自动切换
# r = gevent.spawn(read, q)
# gevent.joinall([w, r])

 

posted @ 2018-05-08 15:45  温良Miner  阅读(1615)  评论(0编辑  收藏  举报
分享到: