zoukankan      html  css  js  c++  java
  • 协程的学习

    这篇博客会用三种方式实现协程

    1、yield

    2、greenlet

    3、gevent

    我们先来看下第一种方式,使用yield实现协程

    1、先来复习一下yield,如果一个函数中有yield,那么他就是一个生成器

    def test():
        print("ok")
        yield
    # 如果一个函数有yield,那么他就不是一个普通的函数,而是一个生成器,这个yield相当于普通函数的return
    
    
    
    test()
    # print(test())
    #如果是一个生成器,那么这段代码不会去执行函数test的,而仅仅是生成一个生成器对象,我们通过print就可以看的出来
    
    # 如果想执行这个生成器,只能生成器对象调用next方法来执行生成器这个函数
    
    # next(test())
    # 这样就执行了上面的生成器的函数,上面这个例子中yield后没有任何值,那么他们返回值就是默认的None,我们用下面的代码就可以获取到yield后面的值
    #在这个例子中,我们可以看到a就可以接受到yield后面的值,我们打印a就可以到a的值为None
    a = next(test())
    print(a)
    
    def test2():
        print(id(test2))
        yield 2
        # next方法执行这里到就会返回一个值2,然后退出函数
    
    b = next(test2())
    print(b)
    # 这里yield后面的值为2,那么我们打印b就可以得到b的值为2
    
    # 如果函数中有一个yield就可以执行一个next方法,如果有2个,就可以执行2个next方法,一次类推
    
    # yield的作用:相当于函数中的return,next方法执行到这里,就会退出,如果在来一个next,就接着yield后面的代码继续执行
    
    
    
    # 上面的例子,只能函数返回值,下面的例子中,我们可以给这个函数传递值
    
    
    def test3():
        print("test23")
        count = yield 3
        print(count)
        yield
    a = test3()
    c = next(a)
    
    # 1、通过next方法进入生成器,执行到yield 3这段代码,就执行结束
    print(c)
    # 这里可以打印c的值为yield后面的值,为3
    
    c = a.send("aa")
    # 这里通过send再次进入,通过send方法传递一个aa进去,这个aa就会赋值给你yield前面的变量,也就是count
    print(c)
    

      

    在来看下通过yiled实现协程,我们用yield来实现一个吃包子的例子

    # 协程:在单线程下实现并发,协程是一种用户态的轻量级线程
    
    
    # 好处
    # 1、无需上下文切换,因为只有一个线程,所以无需要在不同的cpu之间切换
    # 2、无需加锁
    # 3、方便切换控制流,简化编程模型
    # 4、高并发+高扩展+低成本,一个cpu并发上万个协程都是没问题的
    
    # 资源消耗
    # 进程>线程>协程
    
    
    #
    # 不好的地方
    # 1、由于是在单线程下实现的协程,那么他就无法利用多核的优势,可以通过多进程+协程的方式实现,进程可以利用到多核的优势,开多个进程,每个进程开一个线程,在协程在开多个协程来实现
    # 2、如果出现阻塞,则会阻塞整个程序
    import time
    
    def eat():
        print("老子来要吃包子了")
        while True:
            num = yield
            print("我吃的包子是{0}".format(num))
    
    
    
    def  create(name):
        num = 1
        # e1 = eat()
        # next(e1)
        # 1、进入生成器的方式1
        # e1.send(None)
        # 2、进入生成器的方式2
    
    
        # 第一次进入生成器,不能用send传参的方法进入生成器,会报错的,我们只能用2中方式进入生成器
        # 1、next(e1)
        # 2、e1.send(None),用send传参数,传一个none
        print("{0}要来做包子了".format(name))
        while True:
            time.sleep(0.1)
            print("我做的包子是{0}".format(num))
            e1.send(num)
            num = num + 1
    
    if __name__ == '__main__':
        e1 = eat()
        next(e1)
        create("2B",)
    

      

    2、在看通过greenlet实现协程,遇到switch就切换

    # import gevent
    from greenlet import greenlet
    
    
    
    
    def test1():
        print(12)
        g2.switch()
        print(34)
        g2.switch()
    
    def test2():
        print("56")
        g1.switch()
        print("78")
    
    if __name__ == '__main__':
        g1 = greenlet(test1)
        g2 = greenlet(test2)
        g1.switch()
    

      

    3、在看通过gevent实现协程,我们用gevent.sleep模拟io阻塞

    import gevent
    import time
    def test1():
        n = 1
        print("这是test1函数的第{1}句{0}".format(time.ctime(),n))
        gevent.sleep(2)
        print("这是test1函数的第{1}句{0}".format(time.ctime(),n + 1))
    
    def test2():
        n = 1
        print("这是test2函数的第{1}句{0}".format(time.ctime(),n))
        gevent.sleep(1)
        print("这是test2函数的第{1}句{0}".format(time.ctime(),n + 1))
    
    
    
    if __name__ == '__main__':
        gevent.joinall(
            [
                gevent.spawn(test1),
                gevent.spawn(test2)
            ]
        )
    

      

    我们在看一个使用gevent实例协程的爬虫的例子

    import gevent
    from urllib.request import urlopen
    import time
    from gevent import monkey
    monkey.patch_all()
    # 这一句是一个补丁,打上这个补丁,切换就会更快一些,主要是windows上起作用
    
    def test(url):
        print("我要准备跑网址【{0}】".format(url))
        resp = urlopen(url)
        data = resp.read()
        print("网址【{0}】的长度是【{1}】".format(url,len(data)))
    
    
    test_list = ['https://www.python.org/','https://www.126.com/','https://www.baidu.com/']
    
    if __name__ == '__main__':
        start1_time = time.time()
        for i in range(20):
            gevent.joinall(
                [
                    gevent.spawn(test,test_list[0]),
                    gevent.spawn(test, test_list[1]),
                    gevent.spawn(test, test_list[2]),
                ]
            )
        end1_time = time.time()
    
    
        start_time = time.time()
        for i in range(20):
            for url in test_list:
                test(url)
        end_time = time.time()
        print("协程的时间时间是{0}".format(end1_time - start1_time))
        print("函数话费的时间是{0}".format(end_time - start_time))
    

      

  • 相关阅读:
    几种php加速器比较
    细说firewalld和iptables
    Linux上iptables防火墙的基本应用教程
    mysql 字符串按照数字类型排序
    《设计模式之禅》之六大设计原则下篇
    《设计模式之禅》之六大设计原则中篇
    《设计模式之禅》之六大设计原则上篇
    git bash 乱码问题之解决方案
    nexus没有授权导致的错误
    Java之微信公众号开发
  • 原文地址:https://www.cnblogs.com/bainianminguo/p/8270705.html
Copyright © 2011-2022 走看看