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

    8.8 协程

    ​ 我们都知道线程间的任务切换是由操作系统来控制的,而协程的出现,就是为了减少操作系统的开销,由协程来自己控制任务的切换

    ​ 协程本质上就是线程。既然能够切换任务,所以线程有两个最基本的功能:一是保存状态;二是任务切换

    8.8.1 协程的特点

    【优点】

    • 线程任务切换开销小,属于程序级的切换,操作系统感知不到
    • 单线程内就可以实现并发的效果,最大限度的利用CPU

    【缺点】

    • 协程的本质是单线程,无法利用多核;可以一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程
    • 协程是单个线程执行多个任务,一旦协程遇到阻塞,将会阻塞整个线程

    【特点】

    • 必须要在单线程中实现并发
    • 修改共享数据不需要加锁
    • 用户程序内保存多个控制流的上下文栈
    • 一个协程遇到IO操作自动切换到其它协程

    8.8.2 Greenlet

    使用grennlet第三方库实现任务间的切换

    from greenlet import greenlet
    
    def get_money(name):
        print(f"{name} get 10 $")
        g2.switch('jiawen')
        print(f"{name} get 20 $")
        g2.switch()
    
    def buy_goods(name):
        print(f"{name} buy no.1 good ")
        g1.switch()
        print(f"{name} buy no.2 good ")
        g1.switch()
    
    if __name__ == '__main__':
        g1 = greenlet(get_money)
        g2 = greenlet(buy_goods)
    
        g1.switch('gailun')  # switch在第一次时必须要传入参数,以后就不需要了
    

    效率对比

    from greenlet import greenlet
    import time
    
    def f1():
        re = 1
        for i in range(10000000):
            re *= i
            g2.switch()
    
    def f2():
        re = 1
        for i in range(10000000):
            re += i
            g1.switch()
    
    
    if __name__ == '__main__':
        start = time.time()
        g1 = greenlet(f1)
        g2 = greenlet(f2)
    
        g1.switch()  # switch在第一次时必须要传入参数,以后就不需要了
        print(f'{time.time()- start}') # 5.822627305984497
    
    import time
    
    def f1():
        re = 1
        for i in range(10000000):
            re *= i
    
    
    def f2():
        re = 1
        for i in range(10000000):
            re += i
    
    start = time.time()
    f1()
    f2()
    print(f'{time.time()- start}')  # 1.041489601135254
    

    【结论】单纯的切换,在没有IO阻塞的情况下,协程的效率反而降低

    8.8.3 Gevent介绍

    Gevent也是一个第三方库,主要用来实现并发同步或是异步编程,在gevent中用到的主要模式是Greenlet, Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。

    【方法】

    ​ gevent.spawn(func,*args,**kwargs) spawn括号内第一个参数是函数名,后面可以有多个参数,可以是位置实参或关键字实参,都是传给函数func,spawn是异步提交任务

    ​ join() 等待调用者结束

    ​ value() 拿到调用者的返回值

    遇到IO阻塞会自动切换

    import gevent
    
    
    def get_money(name):
        print(f"{name} get 10 $")
        gevent.sleep(2)   #模拟的是gevent可以识别的IO阻塞
        print(f"{name} get 20 $")
    
    
    def buy_goods(name):
        print(f"{name} buy no.1 good ")
        gevent.sleep(1)
        print(f"{name} buy no.2 good ")
    
    
    if __name__ == '__main__':
        g1 = gevent.spawn(get_money,'gailun')
        g2 = gevent.spawn(buy_goods,name='jiawen')
        g1.join()
        g2.join()
        # gevent.joinall([g1,g2])
        print('__main__')
        # 输出
    gailun get 10 $
    jiawen buy no.1 good 
    jiawen buy no.2 good 
    gailun get 20 $
    __main__
    
    

    【注意】如果要使gevent识别所有的io阻塞,放到被打补丁者的前面或者直接写在在文件的最开头写上以下代码

    rom gevent import monkey;monkey.patch_all()
    

    应用

    # 爬虫
    from gevent import monkey;monkey.patch_all()
    import gevent
    import requests
    import time
    
    def get_inf(url):
        print(f'GET:{url}')
        res = requests.get(url)
        if res.status_code == 200:
            print(f"{len(res.text)} get from {url}")
    
    if __name__ == '__main__':
        start_time = time.time()
        gevent.joinall(
            [gevent.spawn(get_inf,'https://www.python.org/'),
        gevent.spawn(get_inf,'https://www.yahoo.com/'),
        gevent.spawn(get_inf,'https://github.com/'),
            ]
        )
        print(f"take {time.time()-start_time} secondes")
    
    仅供参考,欢迎指正
  • 相关阅读:
    【各种排序系列之】归并排序
    【LeetCode练习题】Candy
    【LeetCode练习题】Minimum Window Substring
    【LeetCode练习题】Partition List
    【Java之】多线程学习笔记
    【Java】使用Runtime执行其他程序
    【各种排序系列之】快速排序法
    Bzoj 3389: [Usaco2004 Dec]Cleaning Shifts安排值班 最短路,神题
    Bzoj 1901: Zju2112 Dynamic Rankings 树套树,线段树,平衡树,Treap
    Bzoj 2834: 回家的路 dijkstra,堆优化,分层图,最短路
  • 原文地址:https://www.cnblogs.com/jjzz1234/p/11266061.html
Copyright © 2011-2022 走看看