python 异步编程之协程

时间:2024-11-17 10:04:56

最近在学习python的异步编程,这里就简单记录一下,免得日后忘记。

首先,python异步实现大概有三种方式,多进程,多线程和协程;多线程和多进程就不用多说了,基本上每种语言都会有多进行和多线程的功能;而python异步编程的特点主要是协程。

多进程,多线程和协程其实从本质上来说都是为了提升程序的运行效率,节省资源;因此,三者没用好坏之分,只是不同的场景适合不同的任务。

比如多进程,计算机是以进程为单位进行资源划分的,每一个进程都有其独立的资源管理;而多线程是共享一份进程资源,而线程是CPU调度的最小单位。最后就是协程,协程首先是单线程的,只不过在一些耗时的任务中,单线程会等待任务的完成然后再执行下一步,而协程的作用就是提升单线程的执行效率,在等待的过程中可以继续执行别的任务。

为什么会有协程的出现? 

在传统的异步编程中,大都使用多进程或多线程进行任务的管理与调度,但进程和线程有一个很大的问题就是上下文切换,不论是进程还是线程;但CPU在切换上下文的时候,需要把当前线程或进程的资源进行存储——也就是入栈,而这是需要消耗系统资源的;因此为了提升资源的利用效率,协程就出现了;协程由于是单线程的,因此其就避免了线程切换的资源开销,而由于协程能够在等待任务执行的过程中去执行其它任务,因此其就间接实现了“伪多线程”,也叫做微线程。

那协程是怎么实现的呢?

在python之前的版本中,协程是通过greenlet和yield关键字实现的;而随着python3.4的发布,asyncio被引入进来,而且在之后的版本中又引入了await/async关键字,这使得python协程编程变得更简单,更高效。

在asyncio异步工具包中,有几个关键的词,协程-coroutine,任务-task,event loop-事件循环;

asyncio就是通过事件循环实现的协程,而协程和任务是其两个关键对象;协程对应两种情况——协程函数和协程对象,而async就是用来定义协程对象的;由async定义的函数就是协程函数。

而await关键字是用来,等待任务执行,并通知事件循环来进行任务调度。

async def hello():
    print("开始")
    print(f"started at {time.strftime('%X')}")
    await asyncio.sleep(10)
    print("协程")
asyncio.run(hello())

以上就是最简单的协程代码,但这里并没有实现真正的协程,函数还是会休眠十秒之后才会结束。

要想实现真正的协程,task-任务的作用就出现了;

async def fun():
    print("函数")
    await asyncio.sleep(3)
    print("函数完成")

async def main():
    # await asyncio.sleep(3)
    print("主函数")
    print(f"started at {time.strftime('%X')}")
    task1 = asyncio.create_task(fun())
    task2 = asyncio.create_task(fun())

    await asyncio.gather(task1, task2)
    print(f"started at {time.strftime('%X')}")

asyncio.run(main())

函数fun是一个协程函数,而在主函数中会使用asyncio.create_task函数创建两个任务,而asyncio.gather把两个任务纳入到事件循环的管理中,然后通过ayncio.run函数执行事件循环。

如下图所示,在同步编程中单线程执行fun函数,应该需要3+3六秒的时间才能执行完成;而使用了协程之后,只用了3秒就执行完成。

这就是协程的作用。

在python使用协程编程的过程中,主要使用的就是await/async和asyncio工具包;async用来定义协程函数,await配合async处理协程对象,asyncio用来调度和管理协程对象。

await只能在async关键字定义的函数或对象中使用。

使用async/await来创建协程函数,使用asyncio来创建任务,再用asyncio来执行任务。