zoukankan      html  css  js  c++  java
  • python协程 --- asyncio库

    从线程理解协程

    一个cpu想要同时实现多任务的执行,需要操作系统调度cpu去执行多个线程,每个线程执行不同的单个任务,从而实现多任务的执行。线程是操作系统的资源之一,创建或者销毁线程都由操作系统执行,每个线程都由自己独立的资源,例如临时变量的数据,函数调用的堆栈信息,或者当前线程执行的当前位置,发生线程切换时候,线程会保存这些资源,记录当前执行的状态,当cpu在次对该线程发起调度时,恢复上一次的离开时的状态执行。这些数据以及状态信息,通常称为线程上下文。

     

    每个线程都有自己的线程上下文,而线程之间的切换必将引起上下文的切换,cpu执行一个线程时,这部分数据可以直接从寄存器或者多级缓存中读取,读取速度是可观的。线程的切换意味着另一个线程的被唤醒,新线程的数据需要被加载,就可能会覆盖掉部分的上个线程在寄存器或者缓存的数据,下次原线程恢复时需要重新从内存中读取数据到寄存器。多个cpu的情况下,这种方式情况更为严重,因为一个线程可以被随机的被某个cpu执行,上下文切换更为频繁,通常我们会考虑将一个线程绑定到某个cpu上,使其只被这个cpu调度,从而提高效率。

    协程

    线程的调度更加的耗费系统资源。而使用协程就可以避免这些问题。单个线程的执行是顺序执行的,如果需要完成3个任务(每个任务以一个函数表示),这三个任务将会一个个按照顺序执行,如果每个任务中包含部分IO操作,线程也只能同步阻塞等待IO返回,无法执行其他的任务。而协程实现了在这一个线程中同时调度执行这多个任务,避免这些耗时IO操作的同步阻塞等待,提高了任务的执行效率。

    asyncio基本使用

    官方文档:https://docs.python.org/zh-cn/3/library/asyncio-task.html#running-an-asyncio-program

    定义任务

    定义任务使用了async关键字,然后和普通函数的定义相同

    import asyncio
    
    async task1():
        print("task1 ++++")
        return "abc"
    
    if __name__ = "__main__":
        asyncio.run(task1())    # run方法在3.7版本才实现

    定义了任务task1,然后使用 asyncio.run(task1())执行了该任务。

    在run函数执行任务时,实际上及创建了一个事件循环,这个事件循环会反复监听这个任务的状态,在多个任务时候,还会自动实现多个任务的调度。

    事件循环

    上面使用run函数运行时,会创建一个事件循环,事件循环中可以添加多个任务,并负责调度这些任务,在3.7之前,就需要手动的创建事件循环,添加任务才能保证事件的运行。

    import asyncio
    
    async task1():
        print("task1 ++++")
        return "abc"
    
    if __name__ == "__main__":
        loop = asyncio.get_event_loop()   # 获得一个事件循环
        loop.run_until_complete(task1())   # 运行事件循环,直到task1这个任务结束,事件循环终止

    这里只添加了一个task1任务,如果还存在其他的如task2任务需要一并加入,需要使用gather方法

    async task1():pass
    async task2():pass
    async task3():pass
    
    if __name__ == "__main__":
        loop = asyncio.get_event_loop()   # 获得一个事件循环
        tasks = asyncio.gather(task1(), task2(), task3())
        loop.run_until_complete(tasks)

    运行任务

    任务的运行需要交给事件循环,事件循环可以同时调度多个任务,前提是这些任务存在IO操作。因为协程任务始终是在一个线程上进行调度的,如果一个任务没有IO(例如无限的死循环),始终占据这个线程,其他的任务将无法被调度执行。

    任务可以由事件循环统一调度,这些任务随机的被执行(聚集模式)也可以在一个任务中去启动一个子任务,然后等待子任务执行完毕在继续执行原任务(串行执行模式),等待任务的运行需要使用awati关键字,实现该任务同步阻塞等待子任务的效果。

    import asyncio
    
    async task1():
        print("task1 ++++")
        return "abc"
    
    async task2():
        print("task2 ++++")
        await task1()
        return 123
    
    if __name__ == "__main__":
        loop = asyncio.get_event_loop()   # 获得一个事件循环
        loop.run_until_complete(task2())   # 运行事件循环,直到task1这个任务结束,事件循环终止

    loop事件循环启动task2,task2中调用并同步等待task完成。也可以在task2中将task1添加到事件循环中,两个任务协同调用执行。

    import asyncio
    
    async task1():
        print("task1 ++++")
        return "abc"
    
    async task2():
        t = asyncio.create_task(task1()) # 在loop中添加一个任务,返回任务对象,该任务将会自动被调用执行
        # await t              # 也可以等待该t 任务执行完毕。 
        return 123
    
    if __name__ == "__main__":
        loop = asyncio.get_event_loop()   # 获得一个事件循环
        loop.run_until_complete(task2())   # 运行事件循环,直到task1这个任务结束,事件循环终

    asyncio.sleep()

    该方法也是一个协程对象,该协程对象的功能是等待一定的时常,通常我们在自己的任务中以await的方式调用 asyncio.sleep()方法,使得我们的协程任务等待一定时长再执行,等待时线程可以去执行其他的任务。

    等待对象

    三种等待对象:

      协程对象:async def 方式定义的协程对象

      task对象:使用asyncio.create_task方式返回的对象

      future对象:

    执行回调

    task任务可以被注册一个回调函数,调用时会自动注入该task对象作为参数,所以回调函数只能是一参函数。如果回调函数需要有其他参数,需要提前绑定一部分参数值,使成为一参函数作为回调函数(可以使用装饰器,或者functool中的partail方法绑定实现参数绑定)。

    import asyncio
    import functools
    
    def callback(task, args):
        print(task, args)
        task.result
        return 123
    
    async task1():
        print("task1 ++++")
        return "abc"
    
    async task2():
        t = asyncio.create_task(task1()) # 在loop中添加一个任务,返回任务对象,该任务将会自动被调用执行
        t.add_callback_done(
                functools.partail(callback, args="abc")   # callback函数绑定了一个args="abc"
                )
        asyncio.sleep(1)
        return 123

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

  • 相关阅读:
    社交需求和社交产品的更替
    腾讯产培生面经
    【C++基础】类class
    【C++基础】结构struct
    【C++基础】C-串知识整理
    GeoServer war包在tomcat7中配置遇到的一个问题
    pgrouting 2.0 的环境配置
    阿里2014年9月笔试中的一个算法设计题--擦黑板剩余数字
    VisualSVN Server的启动关闭脚本
    二叉树遍历(前序、中序、后序)的递归及非递归实现(小结)
  • 原文地址:https://www.cnblogs.com/k5210202/p/13070041.html
Copyright © 2011-2022 走看看