1. 异步协程(coroutine)介绍
协程不同于进程和线程(正好这俩我也不懂),是一种特殊的处理方式,允许一个线程在遇到IO等待时间线程不会傻傻等待,利用空闲的时候再去干点其他事情。在python的发展中有四种实现协程的方法,分别是
- greenlet 早期第三方模块
- yield关键字
- asynico 装饰器(py 3.4引入)
- async, await关键字(py3.5) ⭐️ 最新,推荐
1.1 协程的原理
协程的原理就是在执行第一个耗时的函数的时候,去开启执行第二个耗时的函数,直到两个函数都结束。
伪代码的演示如下
def func1():
print(1)
... # 耗时步骤1
print(2)
def func2():
print(3)
... # 耗时步骤2
print(4)
func1()
func2()
首先运行func1()
,在输出1
后,运行到耗时步骤1
,立刻跳转到func2()
,在输出3
后,遇到耗时步骤2
,此时陷入等待,看哪个耗时步骤执行完,如果有另一个func,则去执行另一个。
假设两个耗时步骤
时间一样长,并且只有这两个func
,那么预计的输出就是
1
3
2
4
这里以greenlet的方法讲解协程如何去启动另一个函数
要使用该模块,首先需要pip install greenlet
from greenlet import greenlet
def func1():
print(1) # 第1步:输出1
gr2.switch() # 第3步:切换到func2函数
print(2) # 第6步:输出2
gr2.switch() # 第7步:切换到func2函数,从上一次执行的位置继续向后执行
def func2():
print(3) # 第4步∶输出3
gr1.switch() # 第5步:切换到func1函数,从上一次执行的位置继续向后执行
print(4) # 第8步:输出4
# 生成了特殊的对象,先不会执行
gr1 = greenlet(func1)
gr2 = greenlet(func2)
gr1.switch() #第1步︰去执行func1函数
在greenlet
中,启动另一个函数是手动完成的,也就是switch()
方法,需要启动哪个对象,就调用他的该方法。
上述讲解了协程的基本概念,但是无法运用到实际中,因为人根本无法精准控制什么时候需要跳转去执行另一个函数,因此需要引入async协程框架
1.2 async协程框架
async协程框架
的好处就是,一切都是自动执行的,只要代码运行到一个需要等待的协程对象的时候,就会自动去启动另一个协同对象。这里介绍协程编程中最重要的两个关键字,async
和await
,以及一些框架的用法。
首先需要下载该模块,pip install asyncio
1.2.1 async
async的作用是用来定义协程函数
,就是在之前的函数定义之前加了async
而已,如下面代码所示
# 定义协程函数
async def func():
print('???')
当定义了协程函数
后,它就变得和函数不一样了。在“调用”协程函数的时候,如今只会产生一个携程对象,而不会直接执行函数内部的代码了。
# 得到协程对象
result = func()
在我们平常写的同步代码中,都是执行完一个方法(比如 def函数、 with open)
后再执行另一个方法的,而async
关键字的含义就是将方法
变成能够被中断的、能够被挂起的(这俩等价)
,这样就为协程提供了可能性。
假设有两个异步函数async a,async b,a中的某一步有await,当程序碰到关键字await b()后,异步程序挂起后去执行另一个异步b程序,就是从函数内部跳出去执行其他函数,当挂起条件消失后,不管b是否执行完,要马上从b程序中跳出来(此句存疑),回到原程序执行原来的操作。如果await后面跟的b函数不是异步函数,那么操作就只能等b执行完再返回,无法在b执行的过程中返回。
1.2.2 await
await用来将一个方法
进行挂起
,当执行到该方法中的耗时操作时,就会挂起该方法,等待执行结束,并同时启动另一个方法
。上述这段文字可以描述成事件循环
,将会在接下来介绍。await方法后面+可等待的对象,主要是三类,分别是携程对象
, Future
, Task
。协程对象
就是执行加了async
的函数得到的,上面介绍过了,接下来介绍剩下两种。
1.2.2.1 事件循环
所有被await
的对象都会被装载到任务列表中
,构建成一个事件循环
。事件循环也是协程之所以能够运作的底层原因,所有在列表中的任务都有自己的状态,分为已完成,IO等待中,可执行,正在执行..等
。在事件循环的时候,首先会检查任务列表中的所有任务,将可执行
和已完成
的任务分别返回成列表。对于可执行的任务
,将会依次开始执行,对于已经完成的任务
,则会从事件循环中移除,直到任务列表为空结束。
伪代码如下。
# 伪代码
# 任务都有自己的状态:分为 已完成,IO等待中,可执行,正在执行..等
任务列表=[任务1, 任务2, 任务3, ...]
# 死循环 检查每个任务
while True:
可执行的任务列表,已完成的任务列表 = 去任务列表中检查所有的任务,将'可执行'和'已完成'的任务返回
for 就绪任务 in 可执行的任务列表:
执行已就绪的任务
for 已完成的任务 in 已完成的任务列表:
在任务列表中移除已完成的任务
如果 任务列表 中的任务都已完成,则终止循环
1.2.2.2 Task对象
Task
和Future
,携程对象
一样,都是可被加到任务列表
中的任务
Tasks are used to schedule coroutines concurrently.
When a coroutine is wrapped into a Task with functions like
asyncio.create_task()
the coroutine is automatically scheduled to run soon。
白话:在事件循环中添加多个任务。
Tasks用于并发调度协程,通过asyncio.create_task(协程对象)
的方式创建Task对象,这样可以让协程加入事件循环(接下来会讲)
中等待被调度执行。除了使用 asyncio.create_task()
函数以外,还可以用低层级的 loop.create_task()
或 ensure_future()
函数。不建议手动实例化 Task 对象。
注意:asyncio.create_task()
函数在 Python 3.7 中被加入。在 Python 3.7 之前,可以改用低层级的 asyncio.ensure_future()
函数。
总之,记住这是一个用于事件循环
的成员类型就行了。
创建Task
的代码如下所示
# 这里是 > Python 3.7 的版本的语法
asyncio.create_task( 协程对象 )
# 这里是 Python 3.6 的版本的语法
loop = asyncio.get_event_loop() # 获取事件循环
loop.create_task( 协程对象 )
# 或者
asyncio.create_task
可以运行的示例代码如下所示,包括了事件循环的创建
和Task
的创建,这里没有显式体现任务列表
import asyncio
# 获取事件循环
loop = asyncio.get_event_loop()
# 协程函数
async def func():
print(1)
await asyncio.sleep(2)
print(2)
return "返回值"
async def main():
print("main开始")
# 创建Task对象,将当前执行func函数任务添加到事件循环。
task1 = loop.create_task( func() )
# 创建Task对象,将当前执行func函数任务添加到事件循环。
task2 = loop.create_task( func() )
print("main结束")
# 当执行某协程遇到IO操作时,会自动化切换执行其他任务。
# 此处的await是等待相对应的协程全都执行完毕并获取结果
# 这里的两个await相当于将两个task隐式地加入到了列表中
# ret就是函数的返回值
ret1 = await task1
ret2 = await task2
print(ret1, ret2)
# 执行
loop.run_until_complete(main())
这段代码中显式体现了循环列表
,是真的声明了一个list列表,然后封装成了携程对象
。封装的函数会返回两个值,分别对应着成功执行的任务
的返回值,和没法成功执行的任务
import asyncio
loop = asyncio.get_event_loop()
async def func1():
print(1)
await asyncio.sleep(2)
print(2)
return "返回值"
async def func2():
print(3)
await asyncio.sleep(3)
print(4)
return "返回值"
async def main():
print("main开始")
# 显式声明列表
task_list = [
# asyncio.create_task(func(), name='n1'),
loop.create_task(func1()),
loop.create_task(func2())
]
print("main结束")
# 由于await后面只能加 协程对象,Future、Task对象,因此需要将列表封装成携程对象
# 两个默认返回
# donw:是个集合,两个task的返回值都在这里
# pending:没啥意义
# 设置最多等2s,如果么有完成,pending就是还没完成的那些
# 如果设置为None,就是等待所有的都完成
done,pending = await asyncio.wait(task_list, timeout=3)
print(done, pending)
loop.run_until_complete(main())
也可以直接用一个普通的列表,不用async main
直接执行
import asyncio
loop = asyncio.get_event_loop()
async def func():
print(1)
await asyncio.sleep(2)
print(2)
return "返回值"
# 不创建task
task_list = [
func(),
func(),
]
# 这样子在生成事件循环之后,内部会创建一个
done,pending = loop.run_until_complete(asyncio.wait(task_list))
print(done)
1.2.2.3 Future(asyncio.Future)对象
Future
和Task
,携程对象
一样,都是可被加到列表中的任务
A
Future
is a special low-level awaitable object that represents an eventual result of an asynchronous operation.
Task继承自Future,Task对象内部await结果的处理基于Future对象来的。实际上,Future和Task的用法几乎一样
示例代码 (暂无可用的)
async def main():
# 获取当前事件循环
loop = asyncio.get_running_loop()
# 创建一个任务(Future对象),这个任务什么都不干。
fut = loop.create_future()
# 等待任务最终结果(Future对象),没有结果则会一直等下去。
await fut
asyncio.run( main() )