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性能才能显示出来
    """
    
    
    
    爱,就是你和某个人一起经历的一切。
  • 相关阅读:
    vmware vSphere client中,选择文件->部署OVF模板,报错处理方法
    [POJ 1521]--Entropy(哈夫曼树)
    [HDU 1016]--Prime Ring Problem(回溯)
    [HDU 2553]--N皇后问题(回溯)/N皇后问题的分析
    平面最近点对问题(分治)
    [GDUT 决赛]--GCD,LCM——我是好人(数论)
    [HDU 1428]--漫步校园(记忆化搜索)
    [Swust OJ 643]--行列式的计算(上三角行列式变换)
    [Swust OJ 491]--分数的位置(简单版)
    [Swust OJ 465]--吴奶奶买鱼(0-1背包+dfs)
  • 原文地址:https://www.cnblogs.com/afly-8/p/13561135.html
Copyright © 2011-2022 走看看