• 并发编程--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])
  • 相关阅读:
    StrToUnicode C#语言如何将汉字转换成Unicode字符
    SQLServer 2008数据库查看死锁、堵塞的SQL语句
    String.IsNullOrEmpty和"".length>0 那个更高效
    使用策略设计模式,反射,解决商城系统中的商品折扣问题
    假分页
    MAC校验
    eclipse环境加载svn
    将数据导出Excel格式
    svn提交问题
    字符串前拼接字符
  • 原文地址:https://www.cnblogs.com/pythoner6833/p/9008642.html
Copyright © 2020-2023  润新知