最近在学习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来执行任务。