zoukankan      html  css  js  c++  java
  • python携程

    介绍

    协程(coroutine),又称为微线程,纤程。协程的作用:在执行A函数的时候,可以随时中断,去执行B函数,然后中断继续执行A函数(可以自动切换),单着一过程并不是函数调用(没有调用语句),过程很像多线程,然而协程只有一个线程在执行
    子程序调用总是一个入口,一次返回,调用顺序是明确的。而协程的调用和子程序不同。
    协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。

    注意,在一个子程序中中断,去执行其他子程序,不是函数调用,有点类似CPU的中断。比如子程序A、B:

    def A():
        print(1)
        print(2)
    
    
    def B():
        print(x)
        print(y)
    #假设由协程执行,在执行A的过程中,可以随时中断,去执行B,B也可能在执行过程中中断再去执行A,结果可能是:
    1
    x
    2
    y
    

    但是在A中是没有调用B的,所以协程的调用比函数调用理解起来要难一些。

    优势

    看起来A、B的执行有点像多线程,但协程的特点在于是一个线程执行,那和多线程比,协程有何优势?
    最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。

    第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。

    因为协程是一个线程执行,那怎么利用多核CPU呢?最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。

    yield实现协程

    先来再复习生成器:
    yield是用于生成器。生成器通俗的认为,在一个函数中,使用了yield来代替return的位置的函数,就是生成器。它不同于函数的使用方法是:函数使用return来进行返回值,每调用一次,返回一个新加工好的数据返回给你;yield不同,它会在调用生成器的时候,把数据生成object,然后当需要用的时候,要用next()方法来取,同时不可逆。
    如果一个函数中有return返回值,而且在return下面还有代码:那么return下面的代码将不会被执行,而yield却不同,它返回值后还可以继续执行下面的代码。

    传统的生产者-消费者模型是一个线程写消息,一个线程取消息,通过锁机制控制队列和等待,但一不小心就可能死锁。
    如果改用协程,生产者生产消息后,直接通过yield跳转到消费者开始执行,待消费者执行完毕后,切换回生产者继续生产,效率极高(yield实现):

    import time
    
    def cosumer(name):
        """
        这是一个生成器,调用时候才执行
        :param name: 消费者名字
        :return:包子
        """
        print('消费者准备吃包子')
        while True:
            new_baozi = yield
            print('%s吃第%d个包子'%(name,new_baozi))
            time.sleep(1)
    def producer(name):
        r = con.__next__()
        r = con2.__next__()
        count = 1
        while True:
            print('%s正在生产第%s个包子和%d个包子'%(name,count,count+1))
            # 调用生成器并且发送数据
            con.send(count)
            con2.send(count+1)
            count += 2
    if __name__ == '__main__':
        con = cosumer('李云龙')
        con2 = cosumer('翠花')
        p = producer('大厨')
    
    #结果:
    消费者准备吃包子
    消费者准备吃包子
    大厨正在生产第1个包子和2个包子
    李云龙吃第1个包子
    翠花吃第2个包子
    大厨正在生产第3个包子和4个包子
    李云龙吃第3个包子
    翠花吃第4个包子
    大厨正在生产第5个包子和6个包子
    李云龙吃第5个包子
    翠花吃第6个包子
    大厨正在生产第7个包子和8个包子
    李云龙吃第7个包子
    翠花吃第8个包子
    大厨正在生产第9个包子和10个包子
    李云龙吃第9个包子
    
    1. 首先调用c.next()启动生成器;

    2. 然后,一旦生产了东西,通过c.send(n)切换到consumer执行;

    3. consumer通过yield拿到消息,处理,又通过yield把结果传回;

    4. producer拿到consumer处理的结果,继续生产下一条消息;

    模块greenlet实现

    greenlet是一个用C实现的协程模块,相比与python自带的yield,它可以使你在任意函数之
    间随意切换,而不需把这个函数先声明为generator

    一些方法:
    g = greenlet(run=None, parent=None):实例化一个greenlet对象
    g.parent:每一个协程都有一个父协程,当前协程结束后会回到父协程中执行,该属性默认是创建该协程的协程
    g.run: 该属性是协程实际运行的代码. run方法结束了,那么该协程也就结束了
    g.switch(*args, **kwargs): 切换到g协程
    g.throw(): 切换到g协程,接着抛出一个异常

    from greenlet import greenlet
    
    def work1():
        print(12)
        # 3.切换到test2()中
        gr2.switch()
        print(45)
        gr2.switch()
    def work2():
        print(98)
        gr1.switch()
        print(50)
    
    #1.将要执行的函数封装到greenlet对象
    gr1 = greenlet(work1)
    gr2 = greenlet(work2)
    #2.想要执行哪个就可以使用 对象.swith()
    gr1.switch()
    

    结果:
    在这里插入图片描述
    greenlet模块

    • gr1=greenlet(目标函数)
    • gr1.switch() 切换执行

    模块gevent实现

    gevent模块安装遇到问题:pycharm获取源问题,使用清华大学源下载的。
    Gevent是一种基于协程的Python网络库,它用到Greenlet提供的,封装了libevent事件循环的高层同步API。它让开发者在不改变编程习惯的同时,用同步的方式写异步I/O的代码。
    当我们的程序受限于网络的 IO 阻塞时,gevent 才能真正发挥实力,它提供了方法,可以隐形的交出上下文执行权,这样我们可以在不改变程序结构的情况下来实现协程。

    import requests
    import time
    import gevent
    def f(url):
        print("get",url)
        resp = requests.get(url)
        data = resp.text
        print("%d byets recevied from %s"%(len(data),url))
    
    #1.普通模式
    s=time.time()
    f( "http://www.langlang2017.com/img/banner1.png")
    f( "http://www.langlang2017.com/img/banner2.png")
    f( "http://www.langlang2017.com/img/banner3.png")
    f( "http://www.langlang2017.com/img/banner4.png")
    e = time.time()
    print("普通模式时间",e-s)
    #2.使用gevent模块
    start = time.time()
    gevent.joinall([gevent.spawn(f,"http://www.langlang2017.com/img/banner1.png"),
                    gevent.spawn(f, "http://www.langlang2017.com/img/banner2.png"),
                    gevent.spawn(f, "http://www.langlang2017.com/img/banner3.png"),
                    gevent.spawn(f, "http://www.langlang2017.com/img/banner4.png")])#创建一个普通的greenlet对象并切换
    
    print("gevent时间",time.time()-start)
    """
    如果下载量小的话,普通模式(串行)的下载可能会比gevent模块下载快
    如果下载的数据量大的话,gevent性能才能显示出来
    """
    
    
    
    爱,就是你和某个人一起经历的一切。
  • 相关阅读:
    将vue文件script代码抽取到单独的js文件
    git pull 提示错误:Your local changes to the following files would be overwritten by merge
    vue和uniapp 配置项目基础路径
    XAMPP Access forbidden! Access to the requested directory is only available from the local network.
    postman与newman集成
    postman生成代码段
    Curl命令
    POST方法的Content-type类型
    Selenium Grid 并行的Web测试
    pytorch转ONNX以及TnesorRT的坑
  • 原文地址:https://www.cnblogs.com/afly-8/p/13561135.html
Copyright © 2011-2022 走看看