美文网首页
Python(七十二)多任务异步协程

Python(七十二)多任务异步协程

作者: Lonelyroots | 来源:发表于2022-03-03 22:35 被阅读0次

    11_lxml/01_线程池的基本使用.py:

    """
    
    python有一个非常重要的GIL(global interpreter lock,全局解释器锁)。
    python代码执行由python虚拟机(解释器主循环)来控制。对python虚拟机的访问由GIL控制,GIL保证同一时刻只有一个线程在执行。
    
    由于GIL的限制,python多线程实际只能运行在单核CPU。如要实现多核CPU并行,只能通过多进程的方式实现。
    大部分并行模块中,多进程相当于开启多个python解释器,每个解释器对应一个进程。也有一些并行模块通过修改python的GIL机制突破这个限制。
    
    multiprocessing 多进程并发
    由于Python设计的限制(我说的是咱们常用的CPython)。最多只能用满1个CPU核心。
    
    multiprocessing,你只需要定义一个函数,Python会替你完成其它所有事情。借助这个包,可以轻松完成从单进程到并发执行的转换。
    
    """
    from multiprocessing.dummy import Pool
    import time
    
    def get_html(url):
        print('正在下载',url)
        time.sleep(2)
        if url == 3:
            time.sleep(6)
        print('下载成功',url)
    
    startTime = time.time()
    urlList = [1,2,3,4,5,6,7,8]
    pool = Pool(4)       # 实例化对象,开辟一个进程池
    # 将列表中每一个列表元素传递给get_html进行处理
    pool.map(get_html,urlList)
    print(time.time()-startTime)
    
    """
    总结:
        对应上例中的所面临的可能同时出现的上千甚至上万次的客户端请求,“线程池"或"连接池"或许可以缓解部分压力,但是不能解决所有问题。
        总之,多线程模型可以方便高效的解决小规模的服务请求,但面对大规模的服务请求,多线程模型也会遇到瓶颈,可以用非阻塞接口来尝试解决这个问题。
    """
    
    """
    同步调用:即提交一个任务后就在原地等待任务结束,等到拿到任务的结果后再继续下一行代码,效率低下。
    
    解决同步调用方案之多线程/多进程:
    好处:
        在服务器端使用多线程(或多进程)。多线程(或多进程)的目的是让每个连接都拥有独立的线程(或进程),这样任何一个连接的阻塞都不会影响其他的连接。
    弊端:
        开启多进程或多线程的方式,我们是无法无限制地开启多进程或多线程的:在遇到要同时响应成百上千路的连接请求,
        则无论多线程还是多进程都会严重占据系统资源,降低系统对外界响应效率,而且线程与进程本身也更容易进入假死状态。
    好处:
        很多程序员可能会考虑使用"线程池”或连接池”。“线程池”旨在减少创建和销毁线程的频率,
        其维持一定合理数量的线程,并让空闲的线程重新承担新的执行任务。可以很好的降低系统开销。
    弊端:
        “线程池”和"连接池”技术也只是在一定程度上缓解了频繁调用IO接口带来的资源占用。而且,所谓"池”始终有其上限,
        当请求大大超过上限时,“池”构成的系统对外界的响应并不比没有池的时候效果好多少。所以使用"池”必须考虑其面临的响应规模,并根据响应规模调整“池”的大小。
    """
    

    11_lxml/02_异步.py:

    """
    
    进程 线程 协程
    
       5    5   5+10  5
    擦桌子、扫地、拖地、擦窗户
    如果按照顺序 -> 同步,那么打扫卫生的人就是进程
    如果两个人 那么就有两个进程 速度快一倍
    如果每个人的左右手可以分别用来擦桌子和擦窗户,那么每个进程就有两个线程 打扫又快又好
    
    如果按照顺序一个人做完:5+5+15+5 = 30min
    如果两个人 15min
    
    如果需要更快
        那么可以在等地面干燥时,先去擦窗户,擦完窗户之后再去看看地面有没有干,干了即完成
        如果是一个人打扫房间,整个事件就直接缩短为擦桌子5分钟,扫地5分钟,拖地5分钟,等待5分钟 只需要25分钟
        这就是异步
    
    """
    import asyncio
    import time
    
    async def get_html(url):
        print('正在下载',url)
        print('下载成功',url)
        return url
    
    coroutine= get_html("www.baidu.com")
    print(coroutine)        # 打印一个协程对象<coroutine object get_html at 0x0000020B82F0AF48>
    
    """
        协程就相当于未来需要完成的任务,多个协程就是多个需要完成的任务,多个协程可以进一步封装到一个task对象中,task就是一个储存任务的盒子。
        此时,装在盒子里的任务并没有真正的运行,需要把它接入到一个监视器中使它运行,同时监视器还要持续不断的盯着盒子里的任务运行到了哪一步,这个持续不断
    的监视器就用一个循环对象loop来实现。
    """
    loop = asyncio.get_event_loop()     # 创建一个事件循环,监视器
    loop.run_until_complete(coroutine)      # 将协程对象注册到loop,然后启动loop
    

    11_多任务异步协程/03_多任务异步.py:

    import asyncio
    import time
    
    async def request(url):
        print('正在下载:',url)
        # 在异步协程方法中,如果出现同步模块相关代码,那么就无法实现异步
        # time.sleep(2)     # 这是同步模块方法,不能使用
        # 当在async中遇到阻塞必须加 await 手动挂起
        await asyncio.sleep(2)      # 这是异步模块方法,可以使用
        print('下载完成:',url)
    
    start = time.time()
    # 创建一个多任务的列表
    tasks = []
    for page in range(1,101):
        url = 'https://www.baidu.com/page_'+str(page)
        corroutine = request(url)
        task = asyncio.ensure_future(corroutine)        # 注册任务,不会被运行
        tasks.append(task)
    # for i in tasks:
    #     print(i)
    
    loop = asyncio.get_event_loop()
    loop.run_until_complete(asyncio.wait(tasks))        # 多任务运行写法:asyncio.wait(tasks)
    print(time.time()-start)
    
    """
    
    def request(url):
        # 请求-解析-保存
    
    for page in range(1,101):
        url = 'https://www.baidu.com/page_'+str(page)
        request(url)
        
    https://www.baidu.com/page_01
    https://www.baidu.com/page_02
    
    请求->响应,需要时间,所以请求是个非常典型的IO阻塞。
    
    """
    

    11_多任务异步协程/04_flask服务.py:

    from flask import Flask
    import time
    
    app = Flask(__name__)
    
    @app.route('/aaa')
    def index_aaa():
        time.sleep(2)
        return 'aaa'
    
    @app.route('/bbb')
    def index_bbb():
        time.sleep(2)
        return 'bbb'
    
    @app.route('/ccc')
    def index_ccc():
        time.sleep(2)
        return 'ccc'
    
    if __name__ == '__main__':
        app.run(threaded=True)        # 这表明 Flask 启动了多线程模式,不然默认是只有一个线程的
    

    11_多任务异步协程/04_多任务异步协程.py:

    import asyncio
    from requests_html import HTMLSession
    
    session = HTMLSession()
    
    def requestDef(url):
        return session.get(url)
    
    async def parse(url):
        print("正在下载",url)
        # 因为session模块是一个同步模块
        response = await requestDef(url)
        """
            会报错 object HTMLResponse can't be used in 'await' expression  HTMLResponse对象不能在await表达式中使用
            因为根据官方文档说明,await后面的对象必须是如下格式之一:
            1. A native corroutine object returned from a native coroutine function.
                一个原生 coroutine 对象。
            2,A generator-based coroutine object returned from a function decorated with types.coroutine(),
                一个由 types.coroutine() 修饰的生成器,这个生成器可以返回 coroutine对象。
            3. An object with an await__ method returning an iterator.
                一个包含_await方法的对象返回的一个迭代器。
            
            还是不行,它还不是异步执行,也就是说我们仅仅将涉及IO操作的代码封装到 async 修饰的方法里面是不可行的!
            我们必须要使用支持异步操作的请求方式才可以实现真正的异步,所以这里就需要 aiohttp 派上用场了。
        """
        print('下载成功',response.text)
    
    urls = [
        'http://127.0.0.1:5000/aaa',
        'http://127.0.0.1:5000/bbb',
        'http://127.0.0.1:5000/ccc'
    ]
    tasks = []  # 任务列表
    for url in urls:
        corroutine = parse(url)
        task = asyncio.ensure_future(corroutine)  # 注册任务,不会被运行
        tasks.append(task)
    
    loop = asyncio.get_event_loop()
    loop.run_until_complete(asyncio.wait(tasks))        # 多任务运行写法:asyncio.wait(tasks)
    

    11_多任务异步协程/05_aiohttp实现.py:(需与11_多任务异步协程/05_aiohttp实现.py连用)

    """
    aiohttp 与 requests 的不同方法
        text() 返回字符串形式的响应数据 == text
        read() 返回二进制形式的响应数据 == content
        json() 返回json对象     == json()
    """
    import asyncio
    import aiohttp
    from requests_html import HTMLSession
    
    async def requestDef(url):
        session = aiohttp.ClientSession()       # 等于 session = HTMLSession
        response = await session.get(url=url)
        result = await response.text()
        await session.close()
        return result
    
    async def parse(url):
        print("正在下载", url)
        result = await requestDef(url)
        print('下载成功', result)
    
    # # 加强写法
    # async def parse(url):
    #     print("正在下载",url)
    #     async with aiohttp.ClientSession() as session:
    #         async with await session.get(url=url) as response:
    #             # 注意:获取相应数据操作之前一定要使用await进行手动挂起
    #             print(await response.text())
    #             return await response.text()
    
    urls = [
        'http://127.0.0.1:5000/aaa',
        'http://127.0.0.1:5000/bbb',
        'http://127.0.0.1:5000/ccc'
    ]
    tasks = []  # 任务列表
    for url in urls:
        corroutine = parse(url)
        task = asyncio.ensure_future(corroutine)  # 注册任务,不会被运行
        tasks.append(task)
    
    loop = asyncio.get_event_loop()
    loop.run_until_complete(asyncio.wait(tasks))  # 多任务运行写法:asyncio.wait(tasks)
    

    文章到这里就结束了!希望大家能多多支持Python(系列)!六个月带大家学会Python,私聊我,可以问关于本文章的问题!以后每天都会发布新的文章,喜欢的点点关注!一个陪伴你学习Python的新青年!不管多忙都会更新下去,一起加油!

    Editor:Lonelyroots

    相关文章

      网友评论

          本文标题:Python(七十二)多任务异步协程

          本文链接:https://www.haomeiwen.com/subject/glzurrtx.html