• 协程,greenlet原生协程库, gevent库


    协程简介

      协程(coroutine),又称为微线程,纤程,是一种用户级的轻量级线程。协程拥有自己的寄存器上下文和栈。

    协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来时,恢复之前保存的上下文

    和栈。因此协程能保存上一次调用的状态,每次协程重入时,相当于进入上一次调用的状态。

    在并发编程中,协程与线程类似,每个协程表示一个执行单元,有自己的本地数据,

    与其他协程共享全局数据和其他资源。

      协程需要用户自己来编写调度逻辑,对于CPU的切换来说,协程是单进程,所以CPU不用

    去考虑怎么调度、切换上下文,这就省去了CPU的切换开销,所以协程在一定程度上又好于多线程。

      Python通过yield提供了对协程的基本支持,但是不完全,而使用第三方gevent库是更好的选择,

    gevent提供了比较完善的协程支持。gevent是一个基于协程的Python网络函数库,使用greenlet在

    libev事件循环顶部提供了一个高级别并发性的API。主要特性有以下几点:

    • 基于libev的快速事件循环,Linux上是epoll机制。
    • 基于greenlet的轻量级执行单位。
    • API复用了Python标准库里的内容。
    • 支持SSL的协作式sockets。
    • 可通过线程池或c-ares实现DNS查询。

    目录

     

    yield表达式

    yield协程下的生产者与消费者问题

    greenlet原生协程

    gevent协程


    yield表达式

       在了解协程之前,需要先了解一下生成器中的yield,它不仅可以当做生成器,还能当做一个表达式来使用(yield)

    def func():
        x = (yield)
        print(x)
        x = (yield)
        
    
    g = func()
    print(next(g))  # 这是第一个yield,就暂停了
    g.send('hello world')   # 恢复暂停位置,将第一个yield赋值,
                            # x = hello world,然后又执行到yield,暂停
    
    
    -->
    None
    hello world
    
    Process finished with exit code 0

    需要注意的是:    send跟next一样,可以继续暂停的执行,并把send括号里面的东西变成返回值

            没有next开始,就不能使用send!

    yield协程下的生产者与消费者问题

    import random, time
    
    
    def consumer():
        while True:
            item = (yield)  # 接收send的传递内容
            print('消费了%s' % item)
            time.sleep(1)
    
    
    def producer(consumer): # 主函数
        next(consumer)  # 开启协程
        while True:
            item = random.randint(0, 99)
            print('生产了%s' % item)
            consumer.send(item)     # 发送
            time.sleep(1)
    
    
    c = consumer()  # 生成器对象
    producer(c)   

    greenlet原生协程

      greenlet属于三方库,通过 pip install greenlet 安装

      什么是greenlet呢?

        CPython(标准python)能够通过生成器来实现协程,但使用起来并不方便而python的衍生版Stackless python,实现了原生的协程,便于利用。

        于是将stackless中关于协程的代码单独拿出来做成了CPython的扩展包

        也就是python环境下的原生协程包

      greenlet的价值

        一  高性能的远程协程

        二   语义更加明确的显示切换

        三   直接将函数包装成协程,保持原有代码风格

    greenlet下的生产者与消费者问题

    from greenlet import greenlet
    from time import sleep
    from random import randint
    
    def consumer():
        while True:
            item = p.switch()   # 切换p,开始暂停,等待恢复(只有恢复的时候才能收到数据)
            print('消费了%s' % item)
            sleep(1)
    
    
    def producer():
        while True:
            item = randint(0, 99)
            print('生产了%s' % item)
            c.switch(item)      # 暂停当前协程,并且切换到指定的协程,传参
            sleep(1)
    
    c = greenlet(consumer)  # 直接封装成协程
    p = greenlet(producer)
    c.switch()  # 相当于next的作用,开启协程

     gevent协程

      什么是gevent?

        gevent开始之前可以先了解一下IO多路复用的epoll

        gevent通过封装了libev(基于epoll)和greenlet两个库,可以实现类似线程方式的协程

        gevent = epoll + greenlet

      gevent的价值是什么?

        遇到阻塞就切换到另一个协程继续执行

          一  使用基于epoll的libev来避开阻塞

          二   使用基于gevent的高效协程来切换执行

          三   只有阻塞的时候切换,没有轮询的开销,也没有线程的开销

    实现方法:

      通过 monkey patching功能使得第三方模块变成协作式。

    gevent对协程的支持,本质上是 greenlet在实现切换工作。 greenlet工作流程如下:

    假如进行访问网络的IO操作时,出现阻塞, greenlet就显式切换到另一段没有被阻塞的代码段执行,

    直到原先的阻塞状况消失以后,再自动切换回原来的代码段继续处理。因此, greenlet是一种合理安排的串行方式。

      由于IO操作非常耗时,经常使程序处于等待状态,有了 gevent为我们自动切换协程,

    就保证总有 greenlet在运行,而不是等待IO,这就是协程一般比多线程效率高的原因。

    由于切换是在IO操作时自动完成,所以 gevent需要修改 Python自带的一些标准库,将一些常见的阻塞,

    如 socket、 select等地方实现协程跳转,这一过程在启动时通过 monkey patch完成。

     

    gevent实现并发服务器

    from gevent import monkey; monkey.patch_socket()
    # 猴子补丁,将socket替换成一个封装了epoll的socket
    from socket import socket
    import gevent
    
    
    server = socket()   # 封装好的socket
    server.bind(('', 7788))
    server.listen(1000)
    
    def recv(conn):
        while True:
            recv_date = conn.recv(1024).decode()
            if recv_date:
                print(recv_date)
                conn.send(recv_date.encode())
            else:
                conn.close()
            
    while True:
        conn, addr = server.accept()
        # 生成一个协程,并将conn作为参数传入
        gevent.spawn(recv, conn)

    gevent实现生产者与消费者问题

    import gevent
    from gevent import monkey; monkey.patch_all()
    # all是所有的能切换成协程的地方全部切换,他包含了socket,一般情况下都是用all
    from random import randint
    from time import sleep
    from gevent.queue import Queue  # gevent里面的队列
    
    queue = Queue(3)
    
    def consumer():
        while True:
            item = queue.get()  # 空就阻塞
            print('消费了%s' % item)
            sleep(1)
    
    def producer():
        while True:
            item = randint(0, 99)
            queue.put(item)     # 满就阻塞 epoll阻塞就切换
            print('生产了%s' % item)
            sleep(1)
    
    c = gevent.spawn(consumer)  #封装成协程
    p = gevent.spawn(producer)
    gevent.joinall([c, p])  # 等待传入协程结束
  • 相关阅读:
    多级别过滤器
    MongoDBAppender
    org.slf4j.impl.SimpleLoggerFactory cannot be cast to ch.qos.logback.classic.LoggerContext
    简单引入logback
    Logback configuration
    PatternLayoutEncoder 输出格式
    ConsoleAppender
    FileAppender
    Linux学习笔记
    GitLab CI/CD 学习记录
  • 原文地址:https://www.cnblogs.com/pywjh/p/9516968.html
Copyright © 2020-2023  润新知