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

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

原文地址:https://www.cnblogs.com/k5210202/p/13070041.html